特性的识别

特性想要被识别,是通过反射来实现的

通过反射可以做到的事情

  1. 访问程序集中类型的元数据。其中 包括像完整类型名称和成员名称这样的构造,以及对一个构造进行修饰的任何特性
  2. 使用元数据,在运行时动态调用一个类型的成员,而不是执行编译时绑定

通过反射其他的一些描述

  1. 很多语言在编译之后都会丢弃所有元数据(类型和方法名),但是C#保留了绝大多数的元数据
  2. XmlSerializer ValueType DataBinder都使用了反射技术
  3. 只要设置了恰当的代码访问安全性,反射就可以绕过可访问性规则

使用System.Type访问元数据

提供的获取类型信息的一些方法

1. 获取类型的名称 Type.Name
2. 类型是否是Public的 Type.IsPublic
3. 类型的基类型 Type.BaseType
4. 类型支持的接口 Type.GetInterfaces()
5. 类型所在程序集 Type.Assembly
6. 类型的属性方法字段 Type.GetProperties Type.GetMethods Type.GetFields
7. 修饰类型的特性 Type.GetCustomAttributes()

需要注意的是,Type.GetMethods是拿不到扩展方法的,扩展方式只能作为实现类型的静态成员使用

GetType

object包含一个GetType成员,所有类型都包含该方法。(对象实例)

静态类型是无法实例化的,所以无法调用GetType

typeof

使用typeof创建Sysytem.Type实例

public class TestClass{}
Type type = typeof(TestClass)

反射相关的一些继承关系

PropertyInfo和MethodBase 都继承自MemberInfo,其中MethodBase包含一个MethodInfo的成员

泛型类型的反射

判断类型参数的类型

public class Mutil<T>
{
    public void DoSth(T item)
    {
        Type type = typeof(T);
    }
}

判断类或方法是否支持泛型

Type typeNew = typeof(List<>);
typeNew.ContainsGenericParameters       // 类或方法是否包含尚未设置的泛型参数
typeNew.IsGenericType                   // 类型是否是泛型

为泛型类或方法获取类型参数

typeNew.GetGenericArguments();          // 获取泛型实参(或者类型参数)的列表

nameof操作符 (C#6.0)

返回参数名称,这个活动发生在编译时,技术上讲,这个行为不反射

它有一些好处和优势:

1. 保证了传给nameof的实参是一个有效的编程标识符
2. 一些IDE很好的支持,重命名或者查找引用等
3. 适用于任何编程标识符

注意的一点是,永远返回最终的标识符,比如Preson.Name 返回Name。

仍然可以使用C#5.0 的CallMemberName参数特性获取属性名称

特性

修饰对象

特性可以修饰:类、接口、属性、结构、枚举、委托、事件、方法、构造器、字段、参数、返回值、程序集、类型参数、模块

常用修饰方法

[属性1]
[属性2]
public string Value{get;set;}

[属性1,属性2]
public string Value1{get;set;}

但是这种方法修饰不适用于返回值、程序集、模块

修饰程序集、模块、返回值

限制:程序集、模块必须出现在using执行之后,并在任何命名空间或者类声明之前,返回值特性在方法声明之前

// 程序集
[assembly:特性]

// 模块
[module:特性]

// 返回值
[return:特性]
private bool IsStuct(){
    // ...
}
  1. 还存在class:和method:,但是这两个是可选的
  2. 通常使用不加Attribute后缀,只有在自己定义特性,或者以内联方式使用特性的时候才添加。如typeof(DesAttribute)
  3. 要考虑向有公共信息的程序集应用AssemblyVersionAttribute
  4. 考虑应用AssemblyFileVersionAttribute和AssemblyCopyrightAttribute提供有关程序集的附加信息
  5. 要应用以下程序集信息属性:

    1. System.Reflection.AssemblyTitleAttribute
    2. System.Reflection.AssemblyComponyAttribute
    3. System.Reflection.AssemblyProductAttribute
    4. System.Reflection.AssemblyDescriptionAttribute
    5. System.Reflection.AssemblyFileVersionAttribute
    6. System.Reflection.AssemblyCopyrightAttribute

自定义特性

只要类继承自System.Attribute就变成了特性(规范是在属性后面增加Attribute)

查找特性

调用GetCustomAttributes()方法,第一个参数指定特性类型,第二个参数指定是否检查任何重载的方法。如果不指定类型,返回所有特性。

使用构造器来初始化特性

  1. 特性定义的时候声明构造器填充参数
  2. 应用特性的时候,只有常量和typeof()表达式才允许作为参数使用。(为了保证序列到CIL)

Sytem.AttributeUsageAttribute

限制特性修饰特定构造

// 限定特性修饰属性和类
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
public class TestAttribute : Attribute
{
    // ...    
}
  1. AttributeTargets 限定修饰的类型(默认All)
  2. allowMultiple 是否允许多用(默认false)
  3. inherited 是否可被派生类继承(默认false)

FlagsAttribute

  1. 只能标记枚举,标记的枚举值可以组合使用,并且改变了Tostring和Parse的行为
  2. Tostring转换的文本是无法本地化的
  3. 不会自动指派唯一的标志值,或者检查标志是否具有唯一值。还是要显示指派每个枚举项的值

预定义特性

  1. 会影响编译器的行为,造成编译器有时候会报告错误
  2. 没有运行时代码,内置的编译器支持
  3. 常用的几个预定义特性:

    1. AttributeUsageAttribute 限制特性修饰
    2. FlagsAttribute 标记组合使用枚举
    3. ObsoleteAttribute 废弃函数警告
    4. ConditionalAttribute 消除CIL代码,赋予无操作

ConditionalAttribute

  1. 在编译期间,通过移除调用的方式来影响调用点,如果是跨越程序集调用,不会影响被调用点,而是检测调用点的预处理操作符
  2. 可能会不执行,所以标记了out参数和带返回值的函数无法标记,否则会造成编译错误,所以属性不能被修饰(属性即是方法)
  3. 只能修饰类和方法,但是只有派生自System.Attribute的类使用
  4. 修饰自定义特性的时候,只有调用程序集中定义了条件字符串,才能反射获取到这个属性
#define CONDITION_A                     // 定义了才会走,不然函数无操作

[Conditional("CONDITION_A")]
public void FuncA()
{
    Console.WriteLine("func a executing");
}

ObsoleteAttribute

提示对象已经废弃,默认是警告,可以改成报错

与序列号相关的特性

标记了System.SerializableAttribute的属性,格式化程序就可以对该对象执行反射,复制到一个流

[Serializable]
public class Test
{
    public int Value1;
    [NonSerialized] public int Value2;              // 标记不序列化
}

// 序列化
BinaryFormatter format = new BinaryFormatter();     // using System.Runtime.Serialization.Formatters.Binary
format.Serialize(fileStream, funcClass);

自定义序列化

除了使用特性之外,实现ISerializable接口,然后通过实现接口达到中间操作。比如加密解密

系列化的版本控制

序列化的类,在序列化之后,只增加一个字段也会导致反序列化失败,数据不兼容。

如果需要向后兼容,需要标记字段OptionalFieldAttribute

SerializableAttribut特性的CIL信息

转换为元数据的一个设置位,使其成为所谓的伪特性 —— 在元数据表中对位或字段进行设置的特性。