最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

世界热点评!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 condition1using 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; }    }}

关键词: