• C#反射与自定义特性


      反射在项目中常常会配合特性使用, 所以把这俩个放到一起来说,对于理解他们更有帮助。 反射是通过dll读取类型的信息,通过反射创建对象,调用其中的方法,查找程序集信息等,功能非常多,我们在这里只介绍他的常用的功能。 特性其实比较简单,他可以说是类的一个补充,给一个类多添加了一部分信息。

    一、自定义特性

      .NET允许用户自定义特性,定义的特性并不会影响编译过程,因为编译器不能识别他们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据,这也就很好的配合了反射,通过反射读取特性的信息, 就可以做很多事,下面来看一下用户控制特性的信息:

    • 特性可以应用到哪些类型的程序元素上(类、结构、属性、和方法等)
    • 它是否可以多次应用到同一个程序元素上
    • 特性在应用到类和接口上时,是否由派生类和接口继承
    • 这个特性有哪些必选和可选参数

      上代码看一下特性的使用,特性都需要继承Attribute类:

        //参数1: 枚举,指定特性可以应用到哪些元素上
        //参数2:是否可以有多个
        //参数3:能否被派生类继承
        [AttributeUsage( AttributeTargets.Property,AllowMultiple =false,Inherited =false)]
       public class FieldNameAttribute:Attribute
        {
            private string name;
            public FieldNameAttribute(string name) {
                this.name = name;
            }
        }
        /// <summary>
        /// 测试
        /// </summary>
        [FieldName("test")]
        public class Test {
            [FieldName("test")]
            public int Id { get; set; }
        }

     如果把特性放到类上,编译器就会产生一个错误。

     AttributeUsage特性:

      特性类本身用一个特性类(AttributeUsage)标注,这是微软定义的一个特性,它主要功能用于标识自定义特性可以应用到哪里,上面的代码中可以看出,第一个参数是指定特性的位置,大家可以看一下AttributeTargets枚举的参数, 就可以了解都可以使用在哪些元素了。也可以使用OR运算符(AttributeTargets.Property |AttributeTarget.Field ),当然也可以直接选择all。

     指定特性参数:

      以上的例子中使用构造函数的方式标注,如果没有对应的构造函数,编译器会报错,因为反射会从程序集中读取元数据,并实例化它们表示的特性类,所以需要提供对应构造函数,才能在运行期间实例化指定的特性。

     指定可选参数:

         //参数1: 枚举,指定特性可以应用到哪些元素上
        //参数2:是否可以有多个
        //参数3:能否被派生类继承
        [AttributeUsage( AttributeTargets.Property,AllowMultiple =false,Inherited =false)]
       public class FieldNameAttribute:Attribute
        {
            private string name;
            public string Name { get; set; }
            //public FieldNameAttribute(string name) {
            //    this.name = name;
            //}
        }
        /// <summary>
        /// 测试
        /// </summary>
       // [FieldName("test")]
        public class Test {
            [FieldName(Name ="aaa")]
            public int Id { get; set; }
        }
    接着上边代码修改

      Name属性属于可选参数,可赋值,也可忽略。

    二、反射

      System.Type的中type类,可以获取类型的引用,比如Type t=typeof(double),t是对double类型的引用。Type类是一个抽象的基类,当实例化Type的对象时,实际上是实例化它的一个派生类。

      GetType()方法也可以获取类型的引用,比如:double d=11;Type t=d.GetType();此方法是Object继承而来, 所有对象都会有。还可以调用Type的静态方GetType()获取:Type t=Type.GetType("System.Double");Type 类型下还有很多属性、方法可以用:

         Type t=typeof(double);
         //数据类型名
         var name = t.Name;
          //数据类型的完全限定名
         var fullName = t.FullName;
         //数据类型的名称空间名
         var nameSpace = t.Namespace;
           //该类型的直接基类
          var baseType = t.BaseType;    
    返回的对象类型                          方法                                          
    ConstructorInfo   GetConstructor(),GetConstructors()
    EventInfo GetEvent(),GetEvents()
    FiledInfo GetField(),GetFields()
    MemberInfo GetMember(),GetMembers(),GetDefaultMembers()
    PropertyInfo GetProperty(),GetProperties()

      Assembly类:

        Assembly类在System.Reflection名称空间下定义,它允许访问给定程序集的元数据,和Type类一样 ,它也包含很多加载和执行程序集的属性和方法,方法和属性与Type类似,下面我们来写个反射获取创建对象的实际应用: 

       新建一个控制台项目(myAssembly),添加其余三个类库,如下:

       在IDal接口中声明如下:

     public interface IQueryDal
        {
            void Query();
        }

      在ORAService中声明如下:

     public class OracleServices : IQueryDal
        {
            public void Query()
            {
                Console.WriteLine("oracle查询");
            }
        }

      在SQLService中声明如下:

     public class SqlserServices : IQueryDal
        {
            public void Query()
            {
                Console.WriteLine("sqlser数据查询");
            }
        }

      接着是配置文件:

     <appSettings>
        <add key="Assembly" value="ORAService"/>
        <add key="FullName" value="ORAService.OracleServices"/>
      </appSettings>

      最后是主程序中:

       static void Main(string[] args)
            {
              
                IQueryDal dal = GetDal();
                dal.Query();
                Console.ReadKey();
            }
    
      public static IQueryDal GetDal() {
               var dllName= ConfigurationManager.AppSettings["Assembly"];
               var FullName = ConfigurationManager.AppSettings["FullName"];
               
                Assembly assembly = Assembly.Load(dllName);
                Type t=assembly.GetType(FullName);
    
                IQueryDal dal=(IQueryDal)Activator.CreateInstance(t);
                return dal;
            }

    以上代码需要把ORAService和SQLService的dll 放到主程序中/Bin/Debug中,通过反射创建对象,实现了通过修改配置文件,就可以修改程序运行时要查询的数据库,而不需要修改任何代码; 

     三、反射调用特性

      通过反射获取特性也非常简单,代码虽然简单,但功能缺十分强大, MVC框架就是大量的特性加反射的模式来完成众多的功能,也可以看出特性配合反射,发挥出了更强大的力量。 

      

    [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)]
    //新建一个特性   
     public class MyAttrAttribute : Attribute
        {
            public string LastName { get; set; }
        }
    //测试类  
    public class Test {
            [MyAttr(LastName ="测试特性")]
            public int Id { get; set; }
        }
    //主程序调用 
     Test t = new Test();
       foreach (var p in t.GetType().GetProperties())
                {
                    //判断t实例中是否有某个特性
                    //2参数:是否也查找继承的特性
                    if (p.IsDefined(typeof(MyAttrAttribute), false)) {
                       MyAttrAttribute myattr=(MyAttrAttribute)p.GetCustomAttribute(typeof(MyAttrAttribute));
                      var name= myattr.LastName;
                        //do someing
                        Console.WriteLine(name);
                    };
    
                }
        Console.ReadKey();

     四、总结

        反射是可以通过字符串的形式获取类型信息并创建实例, 调用实例的方法,创建泛型类实例,调用泛型类方法等等,特性是可以给类型添加额外的信息, 像mvc类型的验证特性中就是通过特性来标注,后又通过反射来验证实体数据是否合法,合法则通过验证,在做下一步操作。本文还有很多没有说到,只是简单的应用,有不足的地方还请大家指正。

  • 相关阅读:
    Programming In Lua 第一章
    TCP/IP 第四、五章
    wireshark数据包分析实战 第三、四章
    [MFC.Windows程序设计(第2版) 第一章
    wireshark数据包分析实战 第二章
    C++PrimerPlus第6版 第四章——复合类型
    TCP/IP 第三章
    Linux命令行与脚本编程大全第一章
    Flink的并行度设置
    基于HttpClient的工具类HttpUtil
  • 原文地址:https://www.cnblogs.com/hkf100/p/13511751.html
Copyright © 2020-2023  润新知