反射和特性之特性学习

特性是将额外数据关联到属性(以及其他构造)的一种方式。它不依赖与选项名与属性名的完全匹配,可利用特性指定与被修饰的构造有关的额外元数据。可用特性将一个属性修饰为Required(必须),并提供选项别名。

特性要放到所修饰构造前的一对方括号里,例如:

public class Person
{
    [CommandLineSwitchAlias("name")]
    public string name;

    [CommandLineSwitchRequired]
    public int level;
}

在同一构造上合并多个特性有两种办法,既可在同一对方括号中以逗号分隔多个特性,也可将每个特性放在它自己的一对方括号里,例如:

public class Person
{
    [CommandLineSwitchRequired]
    [CommandLineSwitchAlias("name")]
    public string name;

    public int level;
}

除了修饰属性,特性还可以修饰类、接口、结构体、枚举、委托、事件、方法、构造函数、字段、参数、返回值、程序集、类型参数和模块。大多数构造都可以向上述代码一样使用方括号来应用特性,但该语法不适用于返回值、程序集和模块。

程序集的特性用于添加有关程序集的额外元数据,例如VisualStudio的“项目向导”会生成一个AssemblyInfo.cs文件,其中包含与程序集有关的大量特性。

自定义特性

很容易创建自定义特性,特性是对象,所以定义特性要定义类。从System.Attribute派生后,一个普通类就变成了特性。例如:

public class CommandLineSwitchRequiredAttribute : Attribute
{

}

查找特性

除了提供属性来返回类型成员,Type还提供了一些方法来获取对类型进行修饰的特性。类似的,所有反射类型(比如PropertyInfo和MethodInfo)都包含成员来获取对类型进行修饰的特性列表。

用于查找特性的代码很简单,给定一个PropertyInfo对象(通过反射来获取)调用GetCustomAttributes(),指定要查找的特性,并指定是否检查任何重载的方法。另外也可以调用GetCustomAttributes()方法而不指定特性类型,从而返回所有特性。

使用构造函数初始化特性

调用GetCustomAttributes()返回的是一个object数组,该数组能成功转型为Attribute数组。上面代码的特性没有任何实例成员,所以在返回的特性中,唯一提供的元数据信息就是它是否出现,但特性还可以封装数据。例如:

public class CommandLineSwitchAliasAttribute : Attribute
{
    public string Alias{ get; private set; }

    public CommandLineSwitchAliasAttribute(string alias)
    {
        Alias = alias;
    }
}

public class Person
{
    [CommandLineSwitchAlias("l")]
    public int level;
}

支持该功能需为特性提供一个构造函数,具体的说针对别名需要提供构造函数来获取一个string参数。类似的,如果希望允许多个别名,构造函数要获取string数组作为参数。

向某个构造应用特性时,只有常量值和typeof表达式才允许作为实参。这意味着特性构造函数要求恰当类型的参数。例如提供构造函数来获取System.DateTime类型的实参没有多大意义,因为C#没有System.DateTime常量。

System.AttributeUsageAttribute

大多数特性只修饰特定构造,例如用CommandLineOptionAttribute修饰类或程序集没有意义。为避免不恰当的使用特性,可用System.AttributeUsageAttribute修饰自定义特性(是的,特性可以修饰特性)。例如:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class CommandLineSwitchRequiredAttribute : Attribute
{

}

[CommandLineSwitchRequired]    //报错,提示该特性只能修饰属性、索引器
public class Person
{
    
}

AttributeUsageAttribute的构造函数获取一个AttributeTargets标志(flag)。该枚举提供了“运行时”允许特性修饰的所有目标列表。

具名参数

AttributeUsageAttribute除了能限制特性所修饰的目标,还可指定是否允许特性在一个构造上进行多次拷贝。例如:

[AttributeUsage(AttributeTargets.Property | 
AttributeTargets.Field, 
AllowMultiple = true)]
public class CommandLineSwitchRequiredAttribute : Attribute
{

}

AllowMultiple是具名参数,具名参数在特性构造函数调用中设置特定的公共属性和字段---即使构造函数不包括对应参数。具名参数虽然可选,但它允许设置特性的额外实例数据,同时无须提供对应的构造函数参数。对具名参数的赋值只能放到构造函数的最后一部分进行,任何显式声明的构造函数参数都必须在它之前完成赋值。

有了具名参数后,就可直接对特性的数据进行赋值,而不必为特性属性的每一种组合都提供对应的构造函数。由于一个特性的许多属性都是可选的,所以具名参数许多时候都非常好用。

猜你喜欢

转载自blog.csdn.net/qq_42720695/article/details/124473409