C#图解教程(第四版)小笔记——反射和特性

元数据和反射

元数据(metadata):有关程序及其类型的数据,保存在程序集中。
反射(reflection):一个运行的程序查看自身或其他程序的元数据。需要使用System.Reflection命名空间。
BCL中声明了一个叫做Type的抽象类,包含了类型的特性。使用这个类的对象能获取程序使用的类型的信息。Type是抽象类,实际上访问的是CLR创建的Type(RuntimeType)的派生类。
程序中的每一个类型,都会关联到独立的、由CLR创建的包含这个类型信息的Type类对象。类型的多个实例只关联到一个Type类对象。
C#图解教程(第四版)小笔记——反射和特性_第1张图片

获取Type对象

  1. object的GetType()方法,返回调用者的Type类对象。
  2. typeof(类型t),获取类型t的Type类对象
class MyClass{...}

void static Main{
	MyClass mc = new();
	Console.WriteLine(mc.GetType());		//返回mc的Type对象
	Console.WriteLine(typeof(MyClass));		//返回MyClass的Type对象,不能使用typeof(mc)

特性 Attribute

特性允许我们向程序的程序集添加元数据。它是用于保存程序结构信息的类。
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。
所有特性类都派生自System.Attribute
几个相关术语:
■ 目标:应用了特性的程序结构
■ 消费者:用来获取元数据的程序(如对象浏览器),称为特性的消费者

在源码中将特性应用于程序结构,编译器获取源码,并从特性产生元数据,然后把元数据放到程序集中。
消费者程序可以获取特性的元数据,及程序组其他组件的元数据。
编译器同时生产和消费特性。
C#图解教程(第四版)小笔记——反射和特性_第2张图片
自定义特性名以Attribute结尾,但是应用特性时不需要写Attribute。

应用特性

方式:在结构前放置由方括号括起的特性片段。
应用一个特性是一条声明语句,不会决定在什么时候构造特性类的对象。
C#图解教程(第四版)小笔记——反射和特性_第3张图片

大多数特性只针对直接跟随在其后的结构。
多个特性可以同时使用。
可以显示地标注特性,从而将它应用到特殊的目标结构。
特性目标包含:

event field
method param
property return
type typevar
assembly module

程序集级别(assembly)的特性必须放在任何命名空间之外,且通常放在AssemblyInfo.cs文件中,该文件通常包含有关公司、产品及版权信息的元数据。

[Serializable]		//特性片段
public MyClass{...}

[MyAttribute("simple")]		//带参数的特性片段
public AnotherClass{...}

[Serializable, MyAttribute]		//多个特性可在一个方括号内使用逗号隔开
[My2]							//也可分行写
public ThirdClass{...}

[method: MyAttr("msg")]
[return: MyAttr("ret msg")]		//显示指定特性应用于返回值
public void FuncA(){...}

一些预定义特性

C#图解教程(第四版)小笔记——反射和特性_第4张图片

Obsolete——指定代码过期

使用Obsolete特性将程序结构标注为过期的,并在代码编译时显示警告信息,或直接在使用时产生错误(不能通过编译)。位于System命名空间内。

[Obsolete("warning")]		//设置特性Obsolete,并设置警告信息
void FuncA(){...}

[Obsolete("error", true)]		// 第二个true标记了调用FuncB为错误而不是警告
void FuncB(){...}

// Main内
FuncA();	//正常运行,编译发出警告
FuncB();	//编译不通过,发生错误

Conditional——限定代码调用

用于限定方法的调用,当使用Condition特性并传入一个编译符号参数时,若该编译符号被#define定义,则被修饰的结构能正常调用,若编译符号没有被定义,则编译器会忽略所有这个被修饰结构的调用(定义方法的CIL代码本身仍会在程序集中)。

#define DoTrace		//定义编译符号 DoTrace
using System.Diagnostics;	//Conditional所在命名空间

namespace Program
{
	class MyClass
	{
		[Conditional("DoTrace")]	//当DoTrace被定义时,FuncA可正常调用
		static void FuncA(){...}
		
		[Conditional("DoSth")]		//当DoSth被定义时,FuncB可正常调用
		static void FuncB(){...}
		static void Main()
		{
			FuncA();	//DoTrace已定义,编译器正常调用
			FuncB();	//DoSth未定义,编译器跳过调用
		}
	}
}

获取调用者信息

可以访问源代码信息,只能用于方法中的可选参数:

  • CallerFilePath:访问调用该方法的代码所在文件路径
  • CallerLineNumber:访问调用该方法的代码所在行数
  • CallerMemberName:访问调用该方法的成员的名称

使用这些特性的可选参数,若调用方法时没有显示指定了参数,则系统会提供源代码的文件路径,调用该方法的代码行数,和调用该方法的成员名称。

using System.Runtime.CompilerServices;		//使用所需的命名空间
namespace Program
{
	static void FuncA([CallerLineNumber]int line = 0, 
					  [CallerMemberName]string name = "")
	{
		System.Console.Write("line = {0}, name = {1}", line, name);
	}
	
	static void Main()
	{
		FuncA();
	}
}
//输出
line = 12, name = Main

DebuggerStepThrough

设置代码不进入调试。位于System.Diagnostics内。
可以用于类、结构体、构造函数、方法、访问器。

AttributeUsage

限制特性使用在某个目标类型上,只能应用于自定义特性类。

[AttributeUsage(AttributeTarget.Method)]		//限制FuncAttribute只针对方法
class FuncAttribute: System.Attribute{...}

三个重要的属性:

  • ValidOn
    保存特性能应用到的目标类型的列表。
    构造函数第一个参数必须是AttributeTarget类型的枚举值。使用竖线 | 同时使用多个值。
  • Inherited
    默认为true,指示特性是否会被装饰类型的派生类继承。
  • AllowMultiple
    默认为false,指示目标是否能同时应用特性的多个实例。

C#图解教程(第四版)小笔记——反射和特性_第5张图片

自定义特性

自定义特性类声明基类为System.Attribute,且特性名以Attribute结尾。
自定义特性的公共成员只能是:字段、属性、构造函数。
为了更安全,推荐将特性类声明为sealed
推荐在特些声明中使用AttributeUsage显示指定目标组。

class MyAttribute: System.Attribute
{
	public MyAttribute(string s)	//构造函数也需要写Attribute
	{
		...
	}
}

访问特性

使用Type对象的IsDefined和GetCustomAttributes方法。

IsDefined

用来检测某个特性是否应用到了某个类上。
接受两个参数:
■ 第一个参数指定特性的Type类对象。
■ 第二个参数指示是否搜索参数一的继承树来查找这个特性。

[MyAttribute]
class MyClass{...}

// main内
MyClass mc = new();
Type t = mc.GetType();
bool isDefined = t.IsDefined(typeof(MyAttribute), false);	//判断MyCLass是否被MyAttribute修饰

GetCustomAttributes

返回应用到结构的特性的数组。实际返回object类型的数组。
接受一个参数,指定是否沿继承树搜索。

你可能感兴趣的:(C#小笔记,c#)