C# 6.0本质论(反射、特性和动态编程)

十七、反射、特性和动态编程

17.1 反射

17.1.1 概念及作用

  • 反射
    • 指对程序集中的元数据进行检索的过程
      • 元数据包括类型名称、成员名称和特性
  • 作用
    • 通过反射获取得到的元数据,在运行时可以动态
      • 创建
        • Activator.CreateInstance
        • Assembly.CreateInstance
      • 调用
        • 将类型绑定到现有对象
        • Type.InvokeMember
      • 访问类型实例

17.1.2 System.Type

  • 作用
    • 提供特定类型的有关信息
      • Name
      • IsPublic
      • BaseType
      • GetInterfaces()
      • Assembly
      • GetProperties()、GetMethods()、GetFields()
      • GetCustomAttributes()
  • 获取Type实例对象
    • Object对象.GetType()
      • 根据实例对象获取System.Type的实例
      • 静态类无法实例化
    • typeof()
      • 根据类型名称获取System.Type的实例
      • 可获取静态类的信息

17.1.3 调用成员

  • 利用反射机制可以根据标识符名称调用对应的属性或方法
    • PropertyInfo对象
      • GetValue(object)
        • 获取指定对象的属性值
      • SetValue(object,value)
    • Methodnfo对象
      • Invoke(object obj, object[] parameters)
        • 调用指定对象的对应方法

17.1.4 泛型类型上的反射

  • IsGenericType
    • 判断是否是泛型类型
  • ContainsGenericParameters
    • 判断是否含有未设置的泛型参数
  • GetGenericArguements()
    • 获取泛型参数的类型列表

17.1.5 nameof操作符

  • 获取传入参数的标识符名称

17.1.6 反射实例

public class Example
{
   public static void Main()
   {
      ObjectHandle handle = Activator.CreateInstance("PersonInfo", "Person");
      Object p = handle.Unwrap();
      Type t = p.GetType();
      PropertyInfo prop = t.GetProperty("Name");
      if (prop != null)
         prop.SetValue(p, "Samuel");

      MethodInfo method = t.GetMethod("ToString");
      Object retVal = method.Invoke(p, null);
      if (retVal != null)
         Console.WriteLine(retVal);
   }
}

17.2 特性

17.2.1 概念及作用

  • 作用
    • 在程序集中插入额外的元数据,并将元数据同编程构造(比如类、方法或者属性)关联起来
  • 系统定义的特性可以省略Attribute
    • 例如,FlagsAttribute可以写为Flags
      • 编译器会自动加上

17.2.2 自定义特性

  • 可以自定义特性,继承System.Attribute类
  • 查找特性
    • PropertyInfo.GetCustomAttributes()
      • 返回的对象会使用指定的构造器参数来初始化
  • 使用构造器来初始化特性
public class CommandLineSwitchAliasAttribute : Attribute
{
	public CommandLineSwitchAliasAttribute(string alias)
	{
		Alias = alias;
	}
	public string Alias{ get; private set;}
}

class CommandLineInfo
{
	[CommandLineSwitchAlias("?")]
	public bool Help { get; set; }
}
  • 自定义特性的使用
    • 利用特性,将额外的信息绑定到属性或成员上
    • 通过特性来查找或匹配对应的属性或成员

17.2.3 预定义特性

  • 利用编译器进行功能支持,而不是在运行时利用反射
17.2.3.1 AttributeUsageAttribute
  • 作用
    • 限制特性能应用的目标
      • [AttributeUsage(AttributeTargets.Property)]
    • 是否允许特性在一个构造上进行多次复制
      • [AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
  • 命名参数
    • 可选参数可以通过命名参数的方式出现在构造器调用时的构造参数的最后
17.2.3.2 ConditionalAttribute
  • 作用
    • 类似 #if / #endif
    • 只有调用者中定义了相应的预处理标识符,方法才会执行
# define CONDITION_A
public class Program
{
	public static void Main()
	{
		MethodA();
		MethodB();
	}
	[Conditional("CONDITION_A")]
	static void MethodA()
	{
		Console.WriteLine("MethodA() execting...");
	}
	[Conditional("CONDITION_B")]
	static void MethodB()
	{
		Console.WriteLine("MethodB() execting...");
	}
}	
  • 特点
    • 如#if #endif不同,会编译标注的代码
    • 编译器将所有调用方式移除
  • 只能标注类和方法
    • 标注方法
      • 方法不能有返回值或者out关键字
        • 当方法不能调用时不知道返回什么值
    • 标注类
      • 只能标注派生于Attribute的类
        • 即自定义特性
        • 当调用者定义了预处理标识符时自定义特性才能起作用
17.2.3.3 ObsoleteAttribute
  • 作用
    • 用于标注代码是否过时,编译时会出现警告
    • [Obsolete]
  • [Obsolete(string message)]
    • 接收string参数的表示警告中出现附加信息
  • [Obsolete(string message, bool error)]
    • 接收bool参数的表示警告升级为错误
17.2.3.4 与序列化相关的特性
17.2.3.4.1 SerializableAttribute
  • 标注需要执行序列化的类
17.2.3.4.2 NonSerializableAttribute
  • 标注不需要执行序列化的成员
public static void Main()
{
	Stream stream;
	Document documentBefore = new Document();
	documentBefore.Title = "...";
	Document documentAfter;
	//序列化到二进制文件中
	using(Stream = File.Open(documentBefore.Title + ".bin",FileMode.Create))
	{
		BinaryFormatter formatter = new BinaryFormatter();
		formatter.Serialize(stream, documentBefore);
	}
	//反序列化到对象
	using(Stream = File.Open(documentBefore.Title + ".bin",FileMode.Open))
	{
		BinaryFormatter formatter = new BinaryFormatter();
		documentAfter = (Document)formatter.Deserialize(stream);
	}
}
17.2.3.4.3 自定义序列化
  • 作用
    • 可以在序列化之前对数据进行格式化处理,如加解密
  • 实现ISerializable接口
    • 序列化
      • GetObjectData()方法
        • 接收参数
          • SerializationInfo
            • 键值对形式的集合
            • 一个元素代表一个需要序列化的成员名称及数据
            • 这一过程类似实现JAVA中的writeToParcell()方法
          • StreamingContext
    • 反序列化
      • 提供接收SerializationInfo、StreamingContext参数的构造器
        • 供序列化框架调用生成相应的对象
        • 从SerializationInfo读取存入的数据
[Serializable]
class EncryptableDocument : ISerializable
{
	enum Field
	{	
		Title,
		Data
	}
	public string Title;
	public string Data;
	public void GetObjectData(SerializationInfo info, StreamingContext context)
	{
		info.AddValue(Field.Title.ToString(),Title);
		info.AddValue(Field.Data.ToString(),Data);
	}
	public EncryptableDocument(SerializationInfo info, StreamingContext context)
	{
		Title = info.GetString(Field.Title.ToString());
		Data = info.GetString(Field.Data.ToString());
	}
}
17.2.3.4.4 OptionalFieldsAttribute
  • 作用
    • 将字段标注为可选序列化字段
    • 向后兼容实现版本控制
      • 当原始类型中新增字段后,从已序列化的文件中反序列化得到对象会抛出异常,可以用该特性标注为可选字段
  • 伪特性
    • 包括大部分预定义特性在CIL代码中出现在类定义的内部
    • Serializable特性出现在元数据表的设置位

17.3 使用动态对象进行编程

17.3.1 dynamic

  • dynamic的概念
    • 编译器将有关语句的预期作用的信息一起打包到类型化为 dynamic 的对象或表达式
      • 在运行时,对存储的信息进行检查,任何无效的语句会导致运行 时异常。
    • 如果没有任何调用,dynamic类型声明和object没有区别
      • 一旦调用其成员,会再编译一段代码并注入调用点
  • dynamic的作用
    • 类型转换
      • dynamic可以隐式转化为任意类型
      • 任意类型也可隐式转化为dynamic
    • 简化反射
      • 在Type类型上查找并调用成员的过程可以简化为直接调用成员
      • 效率比直接使用反射高
        • 动态编译
          • 直接注入到调用点
          • 后续调用没有反射和编译开销
public class Example
{
   public static void Main()
   {
      ObjectHandle handle = Activator.CreateInstance("PersonInfo", "Person");
      /*
      Object p = handle.Unwrap();
      Type t = p.GetType();

      MethodInfo method = t.GetMethod("ToString");
      Object retVal = method.Invoke(p, null);
	  */
	  dynamic p = handle.Unwrap();
	  Object retVal = p.ToString();
   }
}

17.3.2 动态绑定

  • 概念
    • 在编译时,根据类无法分析出其包含的具体成员,需要在运行时才能真正分析出来
  • 作用
    • 将类型、成员和操作的解析过程从编译时延迟到运行时
    • 静态编译的优点是类型安全,但当静态编译无法真正发挥作用时,静态编译和动态绑定的功能没有差别
    • 简化代码编写
XELement person = XElement.Parse(
	@"<person>
		<FirstName>Inigo</FirstName>
	 </person>");
//根据XELment类无法分析出其具有的成员
Console.WriteLine(person.Descendants("FirstName").FirstOrDefault().Value);

dynamic person = DynamicXml.Parse(
	@"<person>
		<FirstName>Inigo</FirstName>
	 </person>");
Console.WriteLine(person.FirstName);

17.3.3 自定义动态对象

  • 实现方式
    • 实现System.Dynamic.IDynamicMetaObjectProvider接口
  • 简化方式
    • 继承System.Dynamic.DynamicObject类
    • 只需要重写不合适的方法
  • 自定义动态对象的作用是确定解释机制
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#本质论(第3版) 详细介绍C# 4.0 第1章 c#概述 1.1 hello world 1.2 c#语法基础 1.2.1 c#关键字 1.2.2 类型定义 1.2.3 main 1.2.4 语句和语句分隔符 1.2.5 空白 1.3 使用变量 1.3.1 数据类型 1.3.2 变量的声明 1.3.3 变量的赋值 1.3.4 变量的使用 1.4 控制台输入和输出 1.4.1 从控制台获取输入 1.4.2 将输出写入控制台 1.5 注释 1.6 托管执行和公共语言基础结构 1.7 c#和net版本 .1.8 cil和ildasm 1.9 小结 第2章 数据类型 2.1 基本数值类型 2.1.1 整数类型 2.1.2 浮点类型 2.1.3 decimal类型 2.1.4 字面值 2.2 更多基本类型 2.2.1 布尔类型 2.2.2 字符类型 2.2.3 字符串 2.3 null和void 2.3.1 null 2.3.2 void 2.4 类型的分类 2.4.1 值类型 2.4.2 引用类型 2.5 可空修饰符 2.6 数据类型之间的转换 2.6.1 显式转型 2.6.2 隐式转型 2.6.3 不进行转型的类型转换 2.7 数组 2.7.1 数组的声明 2.7.2 数组的实例化和赋值 2.7.3 数组的使用 2.7.4 字符串作为数组使用 2.7.5 常见错误 2.8 小结 第3章 运算符和控制流 3.1 运算符 3.1.1 一元运算符正和负 3.1.2 二元算术运算符 3.1.3 圆括号运算符 3.1.4 赋值运算符 3.1.5 递增和递减运算符 3.1.6 常量表达式 3.2 流控制概述 3.2.1 if语句 3.2.2 嵌套if 3.3 代码块 3.4 作用域和声明空间 3.5 布尔表达式 3.5.1 关系运算符和相等性运算符 3.5.2 逻辑布尔运算符 3.5.3 逻辑求反运算符 3.5.4 条件运算符 3.5.5 空接合运算符 3.6 按位运算符 3.6.1 移位运算符 3.6.2 按位运算符 3.6.3 按位赋值运算符 3.6.4 按位取反运算符 3.7 控制流语句 3.7.1 whi.1 e和do/while循环 3.7.2 for循环 3.7.3 foreach循环 3.7.4 switch语句 3.8 跳转语句 3.8.1 break语句 3.8.2 continue语句 3.8.3 go to语句 3.9 c#预处理器指令 3.9.1 排除和包含代码 3.9.2 定义预处理器符号 3.9.3 生成错误和警告 3.9.4 关闭警告消息 3.9.5 nowarn:选项 3.9.6 指定行号 3.9.7 可视编辑器提示 3.10 小结 第4章 方法和参数 4.1 方法的调用 4.1.1 命名空间 4.1.2 类型名称 4.1.3 作用域 4.1.4 方法名称 4.1.5 参数 4.1.6 方法返回值 4.1.7 语句与方法调用的比较 4.2 方法的声明 4.2.1 参数声明 4.2.2 方法返回值声明 4.3 uslng指令 4.4 main()的返回值和参数 4.5 参数 4.5.1 值参数 4.5.2 引用参数 4.5.3 输出参数 4.5.4 参数数组 4.6 递归 4.7 方法重载 4.8 可选参数 4.9 用异常实现基本错误处理 4.9.1 捕捉错误 4.9.2 使用throw语句报告错误 4.10 小结 第5章 类 5.1 类的定义和实例化 5.2 实例字段 5.2.1 实例字段的声明 5.2.2 实例字段的访问 5.3 实例方法 5.4 使用this关键字 5.5 访问修饰符 5.6 属性 5.6.1 属性的声明 5.6.2 自动实现的属性 5.6.3 命名规范 5.6.4 提供属性验证 5.6.5 读和只写属性 5.6.6 为取值方法和赋值方法指定访问修饰符 5.6.7 属性作为虚字段使用 5.6.8 属性和方法调用不允许作为ref或out参数值使用 5.7 构造器 5.7.1 构造器的声明 5.7.2 默认构造器 5.7.3 对象初始化器 5.7.4 构造器的重载 5.7.5 使用this调用另一个构造器 5.8 静态成员 5.8.1 静态字段 5.8.2 静态方法 5.8.3 静态构造器 5.8.4 静态属性 5.8.5 静态类 5.9 扩展方法 5.10 封装数据 5.10.1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值