C# 特性【Attribute】【什么是特性?以及特性的一些修饰】

特性【Attribute】是什么?

概念:1. 特性AttriBute:就是一个类,能直接继承/间接继承自AttriBute父类;

           2. 约定俗成用Attribute结尾,标记时就可以省略,eg:[CustomAttribute] ---> [Custom];
           3. 可以用中括号包裹,标记到元素,其实就是调用构造函数【如果父类用了带参数的构造函数,特性调用只能改为以下结构---->[Custom(0)]】;
           4. 然后可以指定属性,字段,修饰参数,返回值,但是方法不可以;

特性无处不在,比如我们经常用在元素上面添加的,[ ] 中括号形式的东西,基本上,我们在工作中遇到的各种的框架里面都有eg :EF-MVC-WCF-Webservice-UniTest-IOC-AOP-SuperSocket,

如果,添加了[Serializable],就表示这个元素可以序列化,那特性究竟是什么呢?我们不妨按F12点进去看看,     

 

可以看到这个特性SerializableAttribute就是一个类,继承于Attribute抽象类,那我们自己动手写一个试试

    public class CustomAttribute : Attribute
    {
    }
    [CustomAttribute]
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public void Study()
        {
            Console.WriteLine($"{this.Name}");
        }
        public string Answer(string name)
        {
            return $"This is {name}";
        }
    }

我们同时又可以修改为以下格式:说明了什么? 说明:用Attribute结尾,标记时就可以省略,eg:[CustomAttribute]  ---> [Custom]

 

 那我们再进一步修改试试,继承CustomAttribute类,调用看看:

    public class CustomAttribute : Attribute
    {
    }
    public class CustomAttriButeChild : CustomAttribute
    {
    }

那我们是不是就可以得到一个:【特性AttriBute:就是一个类,直接继承/间接继承自AttriBute父类】的结论

那特性既然是一个类,那它里面又可以放什么东西呢?

 1.无参构造函数;

    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {
            Console.WriteLine("这是一个无参数构造函数");
        }
    }
    public class CustomAttriButeChild : CustomAttribute
    {
    }

2.int 类型的参数

    public class CustomAttribute : Attribute
    {
        public CustomAttribute(int Id)
        {
            Console.WriteLine("如果只有当前的这个构造函数,继承当前父类的子类会报错,why?");
        }
    }
    public class CustomAttriButeChild : CustomAttribute
    {
        public CustomAttriButeChild() : base(123)
        {
            Console.WriteLine("继承父类的子类报错,因为它继承了父类,但是它只有一个带参数的构造函数,那么调用也必须显示指定调用");
        }
    }

3.无参,int,string 同时存在的情况呢?

    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {
            Console.WriteLine("这是一个无参数构造函数");
        }
        public CustomAttribute(int Id)
        {
            Console.WriteLine("如果只有当前的这个构造函数,继承当前父类的子类会报错,why?");
        }
        public CustomAttribute(string name)
        {
            Console.WriteLine("string类型的构造函数");
        }
    }
    public class CustomAttriButeChild : CustomAttribute
    {
        public CustomAttriButeChild() : base(123)
        {
            Console.WriteLine("继承父类的子类报错,因为它继承了父类,但是它只有一个带参数的构造函数,那么调用也必须显示的指定调用");
        }
    }

 

这是分开调用特性的情况,那我们一起调用呢?

    

    [Custom(0)]--- [Custom]---[Custom()]上面三个分开都可以调用,但是如果同时调用就会提示特性重复,默认情况不允许,那么我怎么可以做到同时使用多个特性呢?我们加"[AttributeUsage]"特性试试

   

  加上这个[AttributeUsage]特性之后编译器,就没有在显示特性重复,是不是说明这个特性影响编译器,

  我们进去看看它里面都有些什么元素,

    

  AttributeTargets.All,表示可以修饰任何目标元素 ,那我们更换一个呢?

  

    为什么会报错??因为AttributeTargets.Method----->只能用来修饰方法

    

    那我们希望它又可以修饰方法,又可以修饰属性,又可以修饰类呢?

    

    [AttributeUsage]特性,影响编译器,它能-----指定修饰的对象------能否重复修饰---修饰的特性子类是否继承 ---> [AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]

   特性还可以指定属性,字段

    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {
        }
        public CustomAttribute(string name)
        {
        }
        public CustomAttribute(int Id)
        {
           
        }

        public string Remake;
        public string Description { get; set; }
        public void Show()
        {

        }
    } 

   

  同时字段还能修饰参数,返回值

        /// <summary>
        /// 特性在方法的参数前面,用来修饰参数的
        /// [return:Custom]还可以修饰返回值
        /// </summary>
        [return: Custom]
        public string Answer([Custom]string name)
        {
            return $"This is {name}";
        }

特性多重修饰写法:

1     //方法一:
2     [Custom()]
3     [CustomAttriButeChild]
4     [Custom(0) ]
5     [Custom(0, Remake= "字段")]//构造函数的传递方式:是直接传值,字段需要带Remake
6     [Custom(0,Remake ="1115",Description = "属性")]
1      //方式二:
2      [return: Custom, Custom, Custom(0), Custom(0, Remake = "1115", Description = "属性")]

 那问题来了,看了这么多,特性到底有什么用???让我们接着往下面探讨

       //程序入口
        static void Main(string[] args)
        {
            Student student = new Student() 
            { 
                Id = 1, Name = "Attribute" 
            };
            student.Study();
            student.Answer("");
        }

跟踪发现写了那么多特性根本就没什么用,我们自定义的特性,看起来好像毫无意义的样子,那框架提供的特性究竟是怎么产生价值的呢??

那我们新建一个studentVip类反编译看看:

    public class StudentVip : Student
    {
        public string VipGroup { get; set; }
        public void DoHomeWork() 
        {
        }
    }

   编译结果展示:

                         

 我们加上我们自定义的特性反编译试试:

    [Custom("123",Remake ="VIP",Description ="Hello!")]
    public class StudentVip : Student
    {
        [Custom("123", Remake = "VIP", Description = "Hello!")]
        public string VipGroup { get; set; }
        [Custom("123", Remake = "VIP", Description = "Hello!")]
        public void DoHomeWork() 
        {
        }
    } 

反编译之后得到的结果:

            

  反编译之后,发现特性会在元素的内部生成.custom的东西,那我们看一下框架里面的特性,加上编译以后又有什么变化呢?

  

   框架特性也是一样,我们C#访问不到,是不是可以理解为特性没有产生任何变化,但框架究竟是怎么产生功能的呢?也就是怎么在程序运行的时候,能够找到特性的呢?---反射

   我们如何在程序运行中用反射去找到特性?可以从类型 属性 方法 都可以获取特性实例,先IsDefined判断检测,通过反射在构造实例,再获取(实例化)

   我们新建一个InvokeCenter类来看看:

    public class InvokeCenter
    {
        /// <summary>
        /// 一定要先IsDefined判断检测,通过反射在构造实例,再获取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="student"></param>
        public static void MangerStudent<T>(T student) where T : Student
        {
            //打印属性
            Console.WriteLine($"{student.Id}_{student.Name}");
            student.Study();
            student.Answer("123");

            Type type = student.GetType();
            //检查特性是否存在
            if (type.IsDefined(typeof(CustomAttribute), true))
            {
                //获取列表找出全部,也可以只找一个type.GetCustomAttribute--这种方式使用场景比较多
                Object[] oAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);
                foreach (CustomAttribute attribute in oAttributeArray)
                {
                    attribute.Show();
                }
                
                //循环所有的属性
                foreach (var prop in type.GetProperties())
                {
                    //如果这个属性包含这个特性
                    //那么我们就获取到包含这个特性属性的列表,它是这个数组集合
                    if (type.IsDefined(typeof(CustomAttribute), true))
                    {
                        Object[] OAttributeProp = type.GetCustomAttributes(typeof(CustomAttribute), true);
                        foreach (CustomAttribute attribute in OAttributeProp)
                        {
                            attribute.Show();
                        }
                    }
                }
                //把所有的方法找出来
                foreach (var method in type.GetMethods())
                {
                    //判断是否具有特性
                    if (type.IsDefined(typeof(CustomAttribute), true))
                    {
                        Object[] oAttributeMethod = type.GetCustomAttributes(typeof(CustomAttribute), true);
                        foreach (CustomAttribute attribute in oAttributeMethod)
                        {
                            attribute.Show();
                        }
                    }
                }
            }
        }
    }
    //前面我们自定义的CustomAttribute特性的部分代码修改
    [AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]
    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {
            Console.WriteLine($"{this.GetType().Name} 无参数构造函数执行");
        }
        public CustomAttribute(string name)
        {
            Console.WriteLine($"{this.GetType().Name} string参数构造函数执行");
            this._Name = name;
        }
        public CustomAttribute(int Id)
        {
            Console.WriteLine($"{this.GetType().Name} int参数构造函数执行");
            this._Id = Id;
        }
        private int _Id = 0;
        private string _Name = "";

        public string Remake;
        public string Description { get; set; }
        public void Show()
        {
            Console.WriteLine($"{this._Id}_{this._Name}_{this.Remake}_{this.Description}");
        }

    }
         //程序入口调用跟踪
        static void Main(string[] args)
        {
            {
                Student student = new StudentVip()
                {
                    Id = 2, Name = "特性"
                };
                InvokeCenter.MangerStudent<Student>(student);
            }
          }

跟踪的结果展示:以及为什么会有对应条数截图的说明:

  

 结论:程序运行时可以找到特性,那就可以发挥特性的作用,提供额外的信息,行为,特性本身是没有用的,需要一个第三方InvokeCenter,在这里主动检测并提供特性,才能提供功能,

           那么框架的特性方式也是一样的,框架里面已经集成完,自己去检测特性,另外,特性是在编译前就已经确定好了,构造函数/属性/字段,都不能用变量

          【所以MVC5-filter 是不能注入的,只有在core里面才提供了注入filter的方式】

原文地址:https://www.cnblogs.com/wangwangwangMax/p/13977052.html