张德长

导航

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; }


    }
}

 

posted on 2023-05-06 17:15  张德长  阅读(124)  评论(0编辑  收藏  举报