特性
特性将声明性功能引入 C#。 但它们只是代码形式的元数据,不会自发起作用。
使用特性,可以声明的方式将信息与代码相关联。 特性还可以提供能够应用于各种目标的可重用元素。
[Obsolete]特性为例,可应用于类、结构、方法、构造函数等。用于声明元素已过时。然后由C#编译器负责检查此特性,并执行响应操作。
添加到代码中
C#中,特性是继承自Attribute基类中的类。 所有继承自 Attribute 的类都可以用作其他代码块的一种“标记”。 例如,有一个名为 ObsoleteAttribute 的特性。 它用于示意代码已过时,不得再使用。 可以将此特性应用于类(比如说,使用方括号)。
1 | [ ] |
如果将类标记为已过时,最好说明已过时的原因和/或改用的类。 为此,可将字符串参数传递给 Obsolete 特性。1
2
3
4
5[ ]
public class ThisClass{}
//此字符串会作为自变量传递给构造函数
//等价于 var attr = new ObsoleteAttribute("some string");
只能向特性构造函数传递以下的简单类型/文本类型参数:
1 | bool, int, double, string, Tpye, enums, etc |
不能使用表达式或者变量。
创建自己的特性
从Attribute基类中继承即可。1
2
3
4
5
6定义
public class MyspecialAttribute: Attribute{}
使用
[ ]
public class SomeOtherClass{}
.net 基类库中的特性(obsolete)会在编译器中触发某些行为。
自定义创建的任何特性只作元数据使用,不会在,不会在执行的特性类中生成任何代码。
使用特性时,只允许将某些类型的参数作为自变量传递。 不过,在创建特性类型时,C# 编译器不会阻止你创建这些参数。 在以下示例中,我使用可正常编译的构造函数创建了特性。但无法将此构造函数和特性语法结合。1
2
3
4
5
6
7
8
9
10public class GotchaAttribute : Attribute{
public GotchaAttribute(Foo myClass, string str) {}
}
//不会报错
[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail{}
//Attribute constructor parameter 'myClass' has type 'Foo',
//which is not a valid attribute parameter type
如何限制特性使用
特性可用于
- Assembly
- 类
- 构造函数
- 委托
- Enum
- Event
- 字段
- 泛型参数
- 接口
- 方法
- 模块
- 参数
- 属性
- 返回值
- 结构
创建特性类时,C# 默认允许对所有可能的特性目标使用此特性。 如果要将特性限制为只能用于特定目标,可以对特性类使用 AttributeUsageAttribute 来实现。
没错,就是将特性应用于特性!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[ ]
public class MyAttributeForClassAndStruct:Attribute{
}
public class Foo
{
// if the below attribute was uncommented, it would cause a compiler error
// [MyAttributeForClassAndStructOnly]
public Foo() {
}
}
编译错误
Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type.
It is only valid on 'class, struct' declarations
如何使用附加到代码元素的特性
特性只作元数据之用。 不借助一些外在力量,特性其实什么用也没有。
若要查找并使用特性,通常需要使用反射。 基本概念就是借助反射,可以在 C# 中编写用于检查其他代码的代码。
使用反射获取累的相关信息
1 | TypeInfo typeInfo = typeof(MyClass).GetTypeInfo(); |
请务必注意,这些 Attribute 对象的实例化有延迟。 也就是说,只有使用 GetCustomAttribute 或 GetCustomAttributes,它们才会实例化。 这些对象每次都会实例化。 连续两次调用 GetCustomAttributes 将返回两个不同的 ObsoleteAttribute 实例。
基类库(BCL)中的常见特性
- [Obsolete]。 此特性已在上面的示例中使用过,位于 System 命名空间中。 可用于提供关于不断变化的基本代码的声明性文档。 可以提供字符串形式的消息,并能使用另一布尔参数将编译器警告升级为编译器错误。
- [Conditional]。 此特性位于 System.Diagnostics 命名空间中。 可应用于方法(或特性类)。 必须向构造函数传递字符串。 如果此字符串与 #define 指令匹配,那么 C# 编译器将删除对该方法(而不是方法本身)的所有调用。 此特性通常用于调试(诊断)目的。
- [CallerMemberName]。 此特性可应用于参数,位于 System.Runtime.CompilerServices 命名空间中。 可用于插入正在调用另一方法的方法的名称。 此特性通常用于在各种 UI 框架中实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class MyUIClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void ExecutePropertyChanged([CallerMemberName] string propertyName = null)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name {
get { return _name;}
set {
if(value != _name) {
_name = value;
ExecutePropertyChanged(); // notice that "Name" is not needed here explicitly
}
}
}
}
反射
反射提供描述程序集、模块和类型的对象(Type 类型)。
可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。
1 | // Using GetType to obtain type information: |
C# 关键字 protected 和 internal 在 IL 中没有任何意义,且不会用于反射 API 中。 在 IL 中对应的术语为“系列”和“程序集”。 若要标识 internal 使用反射的方法,请使用 IsAssembly 属性。 若要标识 protected internal 方法,请使用 IsFamilyOrAssembly。
反射概述
作用:
- 需要访问程序元数据中的特性时。
- 检查和实例化程序集中的类型。
- 在运行时构建新类型。
- System.Reflection.Emit
- 动态构建类型
- 执行后期绑定,访问在运行时创建的类型上的方法。
- Dynamically Loading and Using Types(动态加载和使用类型)