[转]C#---特性与反射
C#---特性与反射
所有 .NET 支持的语言编写出来的程序,在对应的编译器编译之后,会先产出程序集,其主要内容是中间语言 IL 和元数据。
之后,JIT 再将 IL 翻译为机器码(不同机器实现方式不同)。
IL 使得跨平台成为可能,并且统一了各个框架语言编译之后的形式,使得框架实现的代价大大降低了。
比如,.NET 框架有N种语言,那么每种语言都必须有自己的编译器。而 .NET 框架又决定跨 M 种平台,那么,就需要有 M 种 JIT。
如果不存在 IL,则 .NET 框架为了支持 N 种语言跨 M 种平台,需要 MxN 个编译器。
但如果所有 .NET 框架的 N 种语言经过编译之后,都变成相同的形式,那么只需要 M+N 个编译器就可以了。因此,IL 大大降低了跨平台的代价。
.Net中的类都被编译成IL,反射就可以在运行时获得类信息(有哪些方法,字段,构造函数,父类),还可以动态创建对象,调用成员
每个类都对应一个Type对象,每个方法对应一个MethodInfo对象,每一个属性对应一个PropertyInfo对象
这些就是类,方法,属性的元数据对象。和这个类的对象没有直接关系。
这些元数据对象和类的成员有关,和类的对象无关,也就是每一个成员对应一个对象。
类信息对象叫做Type
exe与dll
exe文件与dll文件的主要区别是:exe文件有程序入口,而dll文件则是一种库(Dynamic Link Library)
元数据metadata
描述exe/dll文件的一个数据清单,JIT在编译时就需要读取metadata数据(在 JIT 的编译过程中会执行验证,通过将代码和元数据中的定义进行比对,确定代码的类型安全性。)
使用反射可以获取操作元数据metadata
===================================================================
Type
获取类信息对象Type的方法:
- 从类的对象获取:Type type = person.getType();
- 从类名获取:Type type = typeof(Person); //typeof是关键字
- 从类的全名(命名空间+类名)获取:Type type = Type.GetType(“xxnamespace.Person”);
为什么
所有的对象都实现了GetType方法(所有对象都派生自object,而object实现了GetType方法)
特性
定义
野生定义:特性是一种允许我们向程序的程序集添加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类。
特性可以告诉编译器把程序结构的某组元数据嵌入程序集(如编译器指令和注释、描述、方法、类等其他信息),它可以放置在几乎所有的声明中(但特定的属性可能限制在其上有效的声明类型,是的,你可以使用一种特性去限制其他特性的使用)
下面会说反射,先剧透,反射就是要操作元数据,所以特性和反射常常配合使用。
开胃
对于C/Cpp程序员,条件编译很常见了
#define Debug
...
...
#ifdef Debug
//program
#endif
而在C#中有更优雅的条件编译,可以在想做条件编译的地方用[Conditional(“Debug”)]修饰
#define DEBUG
using System;
using System.Diagnostics;
public class Myclass
{
[Conditional("DEBUG")] //此处使用 [Conditional("Debug")]
public static void Message(string msg)
{
Console.WriteLine(msg);
}
}
class Test
{
static void function1()
{
Myclass.Message("In Function 1.");
function2();
}
static void function2()
{
Myclass.Message("In Function 2.");
}
public static void Main()
{
Myclass.Message("In Main function.");
function1();
Console.ReadKey();
}
}
这里就完成了条件编译,此处**#define DEBUG**,所以**[Conditional(“DEBUG”)]**修饰的方法在编译和执行时会产生下
In Main function
In Function 1
In Function 2
如果 **[Conditional(“Release”)]**则不会有任何结果.
从这个例子就能大致看出特性的作用了:
C# 程序中的类型、成员和其他实体支持使用修饰符来控制其行为的某些方面。例如,方法的可访问性是由 public、protected、internal 和 private 修饰符控制。
C# 整合了这种能力,以便可以将用户定义类型的声明性信息附加到程序实体,并在运行时检索此类信息。程序通过定义和使用特性来指定此类额外的声明性信息。
当然此处Conditional是已经预定义的特性,类似的预定义特性还有Obsolete,简单来说他可以声明一个类或方法为已过时的,并且可以传入已过时的原因
[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}
进一步
除了预定义的特性,我们还可以自定义特性:
所有特性类都派生自标准库提供的 Attribute 基类。以下示例声明了 HelpAttribute
特性,可将其附加到程序实体,以提供指向关联文档的链接。
(声明时特性名称最好以Attribute为后缀)
using System;
public class HelpAttribute: Attribute
{
string url;
string topic;
public HelpAttribute(string url) //引用时还是会像实例化一样调用构造函数,不过只需要调用Help
{
this.url = url;
}
public string Url => url;
public string Topic {
get { return topic; }
set { topic = value; }
}
}
特性的应用方式为,在相关声明前的方括号内指定特性的名称以及任意自变量。如果特性的名称以 Attribute 结尾,那么可以在引用特性时省略这部分名称。例如,可按如下方法使用 HelpAttribute。
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes")]//引用时还是会像实例化一样调用构造函数,不过只需要调用Help
public class Widget
{
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes",
Topic = "Display")]
public void Display(string text) {}
}
此示例将 HelpAttribute 附加到 Widget 类。还向此类中的 Display 方法附加了另一个 HelpAttribute。
特性类的公共构造函数控制了将特性附加到程序实体时必须提供的信息。可以通过引用特性类的公共读写属性(如上面示例对 Topic 属性的引用),提供其他信息。
可以在运行时使用反射来读取和操纵特性定义的元数据。如果使用这种方法请求获取特定特性,便会调用特性类的构造函数(在程序源中提供信息),并返回生成的特性实例。如果是通过属性提供其他信息,那么在特性实例返回前,这些属性会设置为给定值。
具体使用方法在下面:
反射
简单来说,反射—反射工具–操作metadata元数据的工具
通过反射获取特性信息
using System;
namespace Attribute_Reflec
{
public class HelpAttribute: Attribute
{
string url;
string topic;
public HelpAttribute(string url)
{
this.url = url;
}
public string Url => url;
public string Topic
{
get { return topic; }
set { topic = value; }
}
}
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes")]
public class Widget
{
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes",
Topic = "Display")]
public void Display(string text) { }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Type widgetType = typeof(Widget);
Console.WriteLine(widgetType);
object[] widgetClassAttributes = widgetType.GetCustomAttributes(typeof(HelpAttribute), false);
Console.WriteLine(widgetClassAttributes[0]);
if (widgetClassAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}
System.Reflection.MethodInfo displayMethod = widgetType.GetMethod(nameof(Widget.Display));
Console.WriteLine(displayMethod);
object[] displayMethodAttributes = displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);
Console.WriteLine(displayMethodAttributes[0]);
if (displayMethodAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}
}
}
}
*****************补充*****************
属性规范,如:
C#
[Author("H. Ackerman", version = 1.1)]
class SampleClass
在概念上等效于:
C#
Author anonymousAuthorObject = new Author("H. Ackerman");
anonymousAuthorObject.version = 1.1;
但是,直到查询 SampleClass 以获取属性时才会执行此代码。对 SampleClass 调用 GetCustomAttributes 会导致按上述方式构造并初始化一个 Author 对象。
如果类还有其他属性,则其他属性对象的以类似方式构造。然后 GetCustomAttributes 返回 Author 对象和数组中的任何其他属性对象。
之后就可以对此数组进行迭代,确定根据每个数组元素的类型所应用的属性,并从属性对象中提取信息。
*****************补充*****************