C#高级编程--反射与特性
C#高级编程--反射与特性
特性attribute,特性是一种允许我们向程序集添加元数据的语言结构。特性是用于保存程序结构信息的特殊的类;
目标target,应用/添加了特性的程序结构(program construct)叫做目标;
消费者consumer,用来获取和使用元数据/特性的程序叫做特性的消费者;
.NET有很多内置特性,也可以自定义特性;将特性用于描述程序结构;
编译器获取源代码,并从特性产生关于源代码的描述信息(元数据),然后将元数据放到程序集中;
消费者程序读取特性/元数据,编译器既生成特性,同时也消费特性;
Type对象:
对于程序中用到的每一个类型,CLR都会创建一个包含这个类型的信息的Type对象;
不管该类型创建多少个实例,都只有一个Type对象,并且同时关联到该类及该类的所有实例;
获取Type对象的三种方法
分类 |
静态方法 |
关键字 |
实例方法 |
方法 |
Type.GetType() |
typeof() |
object.GetType() |
参数 |
string className |
Class |
无参 |
示例 |
Type t1 = Type.GetType("Person"); |
Type t2 = typeof(Person); |
Person p = new Person(); Type t3 = p.GetType(); |
错误示例 |
//t4 = Type.GetType(Person); //错误:静态方法不能传入类名 //t4 = Type.GetType(p); //错误:静态方法不能传入类对象 |
//t4 = typeof("Person"); //错误:不能传入类名 字符串 //t4 = typeof(p); //错误:不能传入类对象 |
|
//获取type对象的三种方法
//1、Type类的静态方法,只能传入类名字符串作为参数
Type t1 = Type.GetType("Person");
//2、使用typeof关键字,只能传入类名作为参数,而不能传入字符串或者类对象;
Type t2 = typeof(Person);
//3、使用object的实例方法,实例化的对象,调用基类object的GetType方法,无需参数;
Person p = new Person();
Type t3 = p.GetType();
//错误示例
Type t4;
//t4 = Type.GetType(Person); //错误:静态方法不能传入类名
//t4 = Type.GetType(p);//错误:静态方法不能传入类对象
//t4 = typeof("Person");//错误:typeof不能传入类名字符串
//t4 = typeof(p);//错误:typeof不能传入类对象
元数据metadata,有关程序及其类型的数据被称为元数据,它们保存在程序的程序集中;
反射reflection,程序在运行时,可以查看其他程序集或自身程序集的元数据。运行中程序查看元数据的行为叫做反射;
特性目标
All |
所有 |
可以对任何应用程序元素应用属性 |
Constructor |
构造函数 |
可以对构造函数应用属性 |
Method |
方法/函数 |
可以对方法应用属性 |
Property |
属性 |
可以对属性 (Property) 应用属性 (Attribute)。 |
Field |
字段 |
可以对字段应用属性 |
Parameter |
参数 |
可以对参数应用属性。 |
GenericParameter |
泛型参数 |
可以对泛型参数应用属性。 目前,此属性仅可应用于 C#、Microsoft 中间语言 (MSIL) 和已发出的代码中。 |
ReturnValue |
返回值 |
可以对返回值应用属性 |
Delegate |
委托 |
可以对委托应用属性 |
Event |
事件 |
可以对事件应用属性 |
Class |
类 |
可以对类应用属性 |
Struct |
结构体 |
可以对结构应用属性,即值类型。 |
Interface |
接口 |
可以对接口应用属性 |
Enum |
枚举 |
可以对枚举应用属性 |
Assembly |
程序集 |
可以对程序集应用属性 [assembly:MyAttribute(Parameters)] |
Module |
模块 |
可以对模块应用属性。 Module 引用的是可移植可执行文件(.dll 或 .exe),而不是 Visual Basic 标准模块。 [module:MyAttribute(Parameters)] |
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field),
AllowMultiple=false,Inherited=false]
public class FieldNameAtrribute:Attribute{
private string _fieldName;
public FieldNameAtrribute(string fieldName){_fieldName=fieldName;}
}
特性的特点:
- 特性可以用到那些程序元素上(特性目标AttributeTargets);
- 特性是否可以多次使用(AllowMultiple);
- 特性用到基类上或者接口上时,是否允许子类继承(Inherited);
- 特性需要那些参数(包括可选参数和必选参数);
特性分类(按用途):
- 用于编译器的特性(影响编译过程,如条件编译特性Conditional,过期标记特性Obsolete);
- 用于特性类的特性(指定自定义特性类的一些特点,如指定自定义特性用途的特性AttributeUsage);
- 用于标记元数据的自定义特性(为程序元素添加描述信息的元数据);
消费(查找使用)特性的本质:反射从程序集中读取元数据(特性),并实例化他们所表示的特性类。
特性分类(按程序元素)
- 全局特性,应用于程序集,必须显示指定;
- 局部特性,应用于其他程序元素(可以隐式或显示指定目标元素);
使用特性时的注意事项:
- 使用特性时,可以省略特性类名后的Attribute,当然也可以不省略,二者等价;
- 使用特性时,小括号中的参数有两类:位置参数和命名参数;
- 位置参数是指该特性类的构造函数中的参数;
- 命名参数并不是构造函数中的参数,而是该特性类中的公共字段/属性(这一点与普通类不同);
示例代码
#define condition1
#undef condition1
using System;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
namespace ConsoleCore3
{
class Program
{
static void Main(string[] args)
{
AttributeTest();
}
static void AttributeTest()
{
Person p = new Person("tom");
//通过Person对象获取该类的Type对象
Type t = p.GetType();
//判断该程序元素上是否定义(添加)了某个特性
bool isDefined = t.IsDefined(typeof(SerializableAttribute));
Console.WriteLine($"SerializableAttribute defined:{isDefined}");//True
//通过属性名获取属性的元数据(描述信息)
PropertyInfo pInfo_Name = t.GetProperty("Name");
//通过反射获取属性类型
bool isString = pInfo_Name.PropertyType == typeof(String);
Console.WriteLine($"pName is string:{isString}");
//通过反射获取属性值
Console.WriteLine($"Name={pInfo_Name.GetValue(p)}");
//通过反射为属性赋值
pInfo_Name.SetValue(p, "王英");
Console.WriteLine($"Name={p.Name}");
//通过反射获取属性上是否添加(定义)了某个特性
isDefined = pInfo_Name.IsDefined(typeof(FieldNameAttribute));
Console.WriteLine($"FieldNameAttribute defined:{isDefined}");//True
//获取字段上添加的特性对象(元数据)
FieldNameAttribute attr = pInfo_Name.GetCustomAttribute(typeof(FieldNameAttribute)) as FieldNameAttribute;
//从读取的特性中获取元数据
Console.WriteLine($"FieldName={attr.FieldName},Comment={attr.Comment}");
//通过反射获取某个方法
MethodInfo mInfo_SayHi = t.GetMethod("SayHi");
//通过反射调用方法
mInfo_SayHi.Invoke(p, null);
//通过反射获取某个方法--有参数
MethodInfo mInfo_Eat = t.GetMethod("Eat");
//调用有参方法
mInfo_Eat.Invoke(p, new object[] { "馒头" });
//输出结果
//SerializableAttribute defined:True
//pName is string:True
//Name = tom
//Name = 王英
//FieldNameAttribute defined:True
//FieldName = 姓名, Comment = 这是姓名属性
//hello world
//馒头 真好吃
}
static void UnsafeTest()
{
unsafe
{
// 错误 CS0208 无法获取托管类型(“Person”)的地址和大小,或者声明指向它的指针
//int size = sizeof(Person);
int size = sizeof(double);//8
Console.WriteLine(size);
Student s = new Student();
//错误 CS0208 无法获取托管类型(“Program.Student”)的地址和大小,或者声明指向它的指针
//Student* sp = & s;
}
var p = new Person();
//CS0233 “Person”没有预定义的大小,因此 sizeof 只能在不安全的上下文中使用
// int size = sizeof(Person)
}
unsafe struct Student
{
string name;
int age;
}
static void AssemblyTest2()
{
const string className = "mynamespace.Calculator";
Assembly ass = Assembly.LoadFrom(@"D:\VSFile2019\ConsoleCore1\calculator\bin\Debug\netcoreapp3.1\calculator.dll");
//使用Invoke调用函数
//CreateInstance的参数string typeName,必须是 命名空间.类名 的格式;
//typeName参数区分大小写
object cal = ass.CreateInstance(className);
Console.WriteLine(cal);
//calculator.Calculator
//GetMethod的参数name区分大小写;
//Invoke第一个参数是类的实例化对象,第二个参数是所调用的函数的参数列表,以object数组形式传递
//object[]中的元素个数必须和函数所需参数个数完全一致,否则报错;
//object[]中的元素类型必须与参数与类型一致,或者能够隐式转换为目标类型,否则报错;
object res = cal.GetType().GetMethod("Add").Invoke(cal, new object[] { 1.5, 'a' });
Console.WriteLine(res);//3.5
//使用动态类型调用函数
dynamic calculator = ass.CreateInstance(className);
//动态类型没有智能提示,容易出现拼写错误
dynamic result = calculator.Add(2.2, 3.0);
Console.WriteLine(result);//5.2
//调用一个不存在的函数,编译器不会报错,只有等到运行时才会出现异常;
//:“'mynamespace.Calculator' does not contain a definition for 'Pow'”
//calculator.Pow(2, 3);
//大小写拼写错误,编译器不会有智能提示,只有等到运行时才会出现异常;
//:“'mynamespace.Calculator' does not contain a definition for 'add'”
calculator.add(5, 6);
}
static void AssemblyTest()
{
//通过dll名称 获取程序集
Assembly ass = Assembly.Load("itextsharp");
//itextsharp, Version=5.5.7.0, Culture=neutral, PublicKeyToken=8354ae6d2174ddca
Console.WriteLine(ass);
// Console.WriteLine("types--------------");
// ass.GetTypes().ToList().ForEach (c => Console.WriteLine(c)) ;
Assembly ass2 = Assembly.LoadFile(@"D:\VSFile2019\仲裁文档标准化项目V1.0\AxInterop.DSOFramer.dll");
//AxInterop.DSOFramer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=null
Console.WriteLine(ass2);
ass2.GetTypes().ToList().ForEach(c => Console.WriteLine(c));
Console.ReadLine();
}
static void TypeTest2()
{
Type t = typeof(Person);
Console.WriteLine(t.BaseType); //System.Object
t.GetConstructors().ToList().ForEach(c => Console.WriteLine(c));//Void .ctor()
Console.WriteLine("properties--------------");
t.GetProperties().ToList().ForEach(c => Console.WriteLine(c));
Console.WriteLine("fields--------------");
t.GetFields().ToList().ForEach(c => Console.WriteLine(c));
Console.WriteLine("members--------------");
t.GetMembers().ToList().ForEach(c => Console.WriteLine(c));
//System.Object
//Void.ctor()
//properties--------------
//Int32 Age
//System.String Name
//fields--------------
//Int32 id
//members--------------
//Int32 get_Age()
//Void set_Age(Int32)
//System.String get_Name()
//Void set_Name(System.String)
//Void SayHi()
//System.Type GetType()
//System.String ToString()
//Boolean Equals(System.Object)
//Int32 GetHashCode()
//Void.ctor()
//Int32 Age
//System.String Name
//Int32 id
}
static void TestType()
{
//获取type对象的三种方法
//1、Type类的静态方法,只能传入类名字符串作为参数
Type t1 = Type.GetType("Person");
//2、使用typeof关键字,只能传入类名作为参数,而不能传入字符串或者类对象;
Type t2 = typeof(Person);
//3、使用object的实例方法,实例化的对象,调用基类object的GetType方法,无需参数;
Person p = new Person();
Type t3 = p.GetType();
//错误示例
Type t4;
//t4 = Type.GetType(Person); //错误:静态方法不能传入类名
//t4 = Type.GetType(p);//错误:静态方法不能传入类对象
//t4 = typeof("Person");//错误:typeof不能传入类名字符串
//t4 = typeof(p);//错误:typeof不能传入类对象
}
//条件编译特性,该特性使用者为编译器
//因此只需在对应方法上标记该特性,而无需手动通过反射获取该特性
//如果定义了宏 condition1,则编译该方法,否则编译器忽略该方法
[Conditional("condition1")]
static void TraceMessage(string s)
{
Console.WriteLine(s);
}
static void ConditionalTest()
{
TraceMessage("开始");//如果定义了宏#define condition1,编译器就编译本行代码,否则就忽略
Console.WriteLine("Hello World!");
TraceMessage("结束");//如果定义了宏#define condition1,编译器就编译本行代码,否则就忽略
}
}
[Serializable]
class Person
{
// [FieldName("Age")] 该特性只能在属性上使用,而不能在字段上使用AttributeTargets.Property
//如果想要在多种程序元素上使用,可以使用按位或运算符,连接各种目标元素
//AttributeTargets multiTargets = AttributeTargets.Field|AttributeTargets.Property|AttributeTargets.ReturnValue;
public Person()
{
}
public Person(string name)
{
Name = name;
}
public int id;
[FieldName("Age")]
// [FieldName("Age")] 该特性不能在同一个程序元素上重复使用 AllowMultiple = false
public int Age { get; set; }
[FieldName("姓名", Comment = "这是姓名属性")]
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine("hello world");
}
public void Eat(string food)
{
Console.WriteLine($"{food} 真好吃");
}
}
//用于描述特性用途的特性
// AttributeTargets特性目标,描述该特性可以用于哪些程序元素
//AllowMultiple 是否允许在同一个程序元素上添加多次
//Inherited 是否允许子类继承该特性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldNameAttribute : Attribute
{
public string Comment { get; set; }
private string _fieldName;
public FieldNameAttribute(string fieldName)
{
_fieldName = fieldName;
}
public string FieldName { get => _fieldName; set => _fieldName = value; }
}
}