还记得特性和反射吗(一)

Attribute,特性,是可以为代码提供额外信息的方式,同时也可以为代码标记信息并由外部读取,以此影响被“修饰(decorating)”的代码的使用方式。

一些例子:

Attribute 通过 System.Attribute 类进行派生,要注意的是

  1. 要将定义的特性应用到什么类型的目标(类,属性或其他)
  2. 是否可以对同一目标多次应用。

另外

  1. 在特性应用到类或接口上时,是否由派生类和接口继承,
  2. 必选和可选参数

而这些也是通过应用特性来进行配置:AttributeUsageAttribute,其中 AttributeTargets 是一个枚举类型,用来指定可以应用到哪些程序元素上,AllowMultiple 指定是否可以多次应用,Inherited 参数表示是否自动应用到派生类或接口上。

在《C# 入门经典(第7版)》中的例子中,仅仅表现了如何通过 Type.GetCustomAttributes() 方法来获取特性并使用其信息。

1
2
3
4
5
6
7
8
9
10
11
12
[AttributeUsage(AttributeTarget.Class|AttributeTarget.Method, AllowMultiple = false)]
class DoesInterestingThingsAttribute : Attribute
{
// 传递参数给特性的构造函数
public DoesInterestingThingsAttribute(int howManyTimes)
{
HowManyTimes = howManyTimes;
}
// 定义了特性的可选参数,在使用时虽然被"()"包含,但不会传递给构造函数,会查找有该名称的公共属性或字段
public string WhatDoesItDo {get;set;}
public int HowManyTimes {get;private set}
}

(看到人说,照着别人的代码敲一遍除了能看得仔细点没别的用处,那我就复制粘贴吧hhhh,其实这个是书上的例子,这里用来备忘,方便查看,而示例代码是用来试用来看不是用来“照着敲”的)

使用:

1
2
[DoesInterestingThings(1000, WhatDoesItDo = "voodoo")] // 如果不在特性中定义此公共属性,而是在类 DecoratedClass 中一样能找到吗?《高级编程》中的警告
public class DecoratedClass {}

访问:

1
2
3
4
5
6
7
8
9
10
Type classType = typeof(DecoratedClass);
object[] customAttributes = classType.GetCustomAttributes(true);
foreach(object customAttribute in customAttributes)
{
DoesInterestingThingsAttribute interestingAttribute = customAttribute as DoesInterestingThingsAttribute;
if (interestingAttribute != null)
{
print($"This class does {interestingAttribute.WhatDoesItDo} x "+$" {interestingAttribute.HowManyTimes}!");
}
}

通过这个小栗子,可以从直观上理解如何创建以及使用特性。


Reflection,反射,是用来读取特性的值,可以在运行时动态检查类型信息。

反射可以获取保存在 Type 对象中的使用信息,以及通过 System.Reflection 名称空间中的类型来获取不同的类型信息。反射技术可以从 Type 对象中获取成员信息,基于此获取特性信息。当获取到不同特性时,代码中即可对不同特性信息采取不同的操作。


在《C# 高级编程》中有更进一步的讲解,我一直在思考怎么样读书才是最佳的方式,应该是看多的知识不能忘记吧,最起码,能找到书中对应每一个术语或知识点的位置。

在第16章中,主题是“反射、元数据和动态编程”,讨论了自定义特性、反射和动态编程,当然反射的功能非常庞大,书中也只给出了常用的功能介绍。

首先要介绍反射功能的入口 System.Type。

1
2
3
4
5
6
7
// typeof 运算符
Type t1 = typeof(double);
// 在一个变量上调用 GetType() 方法
double d = 10;
Type t2 = d.GetType();
// 静态方法
Type t3 = Type.GetType("System.Double");

简单来说,Type 有三类属性,字符串属性,引用属性和布尔属性,而 Type 的方法一般用于获取对应数据类型的成员信息(构造函数,属性,方法,事件等)。可以参考这里。

特性可以应用在程序元素上,而程序集 Assembly 也是其中之一,用在程序集上的特性可以在程序集的任意位置。Assembly 类定义在 System.Reflection 名称空间中,用来访问给定程序集的元数据(现在看基本等价于特性),同时可包含了加载和执行程序集的方法。

1
2
Assembly a1 = Assembly.Load("<AssemblyName>");
Assembly a2 = Assembly.LoadFrom(@"C:\<Path>\<AssemblyName>")

与 Type 类似,Assembly 类也可以获得相应程序集上定义的类型的详细信息,Assembly.GetTypes() 方法,返回一个 System.Type 的引用数组。

用于查找在程序集或类型中定义了哪些自定义特性的方法,取决于与该特性相关的对象类型。通过调用 Attribute 类的静态方法 GetCustomAttributes() 可以查找程序集从整体上关联了哪些自定义特性。

1
2
3
4
// 获取程序集整体上关联的自定义特性,获取程序集定义的所有自定义特性
Attribute[] definedAttributes = Attribute.GetCustomAttributes(assembly1);
// 获取感兴趣的特性类的 Type 对象的所有特性
Attribute[] supportsAttribute = Attribute.GetCustomAttributes(assembly1, typeof(SupportsWhatsNewAttribute));

在 C# 6.0 中,还没有这种用法,可以使用 Assembly 对象的实例方法获取。文档在这里。