特性

1、什么是特性

  特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。

  特性具有以下属性:

  1. 特性向程序添加元数据。 元数据是程序中定义的类型的相关信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义特性来指定所需的其他任何信息。
  2. 可以将一个或多个特性应用于整个程序集、模块或较小的程序元素(如类和属性)。
  3. 特性可以像方法和属性一样接受自变量。
  4. 程序可使用反射来检查自己的元数据或其他程序中的元数据。 

2、如何使用特性和声明特性

  2.1 使用特性

    C#语言中内置了许多特性,比如SerializableAttribute、ObsoleteAttribute等,前者标识类可以被序列化,后者标识过时的。.NET有一个约定,所有的特性应该都是以Attribute来结尾,在标记的时候,如果没有加Attribute,编译器会自动寻找带有Attribute的版本,所以在标记的时候我们可以省略Attribute。下面我们使用一下这两个特性。

    [Serializable]
    public class AttributeTest
    {
        [Obsolete("Don't use Old method, use New method.")]
        public void OldMethod()
        {

        }

        public void NewMethod()
        {

        }
    }

    如上代码,标识了[Obsolete]特性的方法,在编译时就会有警告: 'AttributeTest.OldMethod()' is obsolete: 'Don't use Old method, use New method.' 说明该特性是直接影响了编译器的。而标识了[Serializable]特性的对象可以被序列化,是影响了程序的运行。

  2.2 声明特性

    C#提供了System.Attribute类,所有特性类都应改直接活间接继承自System.Attribute类。接下来我们自定义一个特性类。

    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {

        }

        public int Id { get; set; }
        public string Name { get; set; }
        public string Remark { get; set; }
    }

    很简单吧,上面我们就定义了一个特性类,下面我们使用一下自定义的特性。像上面用系统预定义的特性类一样,我们给NewMethod方法加上我们自定义的特性。

    [Serializable]
    public class AttributeTest
    {
        [Obsolete("Don't use Old method, use New method.")]
        public void OldMethod()
        {

        }

        [Custom(Id = 1, Name = "NewMethod", Remark = "This is a NewMethod.")]
        public void NewMethod()
        {

        }
    }  

3、控制特性的使用

  上面我们说了如何定义和使用特性,但有点小问题,如果我们声明的特性只想标记字段和属性,应该怎么办?同一个特性我们想标记两遍怎么办?带着问题我们继续往下看。

  3.1 AttributeUsage的定义

    AttributeUsage用于描述可以在其中使用特性类的方式。可以F12看一下这个类,它的全名是AttributeUsageAttribute,说明它也是一个特性类,所以简单直白的来说它就是用来描述特性的特性。

  3.2 AttributeUsage的介绍

  它里面有三个属性:

    1. ValidOn:标识指示的属性可应用到的程序元素。默认值是 AttributeTargets.All。
    2. AllowMultiple:指示能否为一个程序元素指定多个指示属性实例。如果为 true,则该特性是可以重复标记的。默认值是 false。
    3. Inherited:指示指示的属性能否由派生类和重写成员继承。如果为 true,则该特性可被派生类继承。默认值是 false。

  3.3AttributeUsage的使用

    介绍了AttributeUsage,下面我们来解决一下开头提的两个问题;如果项目中有不同的需求,可以根据需求来定义。

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {

        }

        public int Id { get; set; }
        public string Name { get; set; }
        public string Remark { get; set; }
    }

4、通过反射获取特性

  看完上面的内容,相信大家都会有些疑惑,我声明了特性,也标记了特性,可它没起什么作用,难道仅仅是为了好看。接下来我们演示如何获取特性,当然,获取到特性后就想做什么都可以啦。

  4.1 获取特性

    特性必须通过反射进行获取、使用。下面代码演示了获取类、方法、属性上的特性,并调用特性方法。

 1         public static void Manager<T>(this T test) where T : AttributeTest
 2         {
 3             Type type = test.GetType();
 4 
 5             // 查找类上的特性
 6             if (type.IsDefined(typeof(CustomAttribute), true))
 7             {
 8                 object[] oAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);
 9                 foreach (CustomAttribute attribute in oAttributeArray)
10                 {
11                     attribute.Show();
12                 }
13             }
14 
15             // 查找属性上的特性
16             foreach (var prop in type.GetProperties())
17             {
18                 if (prop.IsDefined(typeof(CustomAttribute), true))
19                 {
20                     object[] oAttributeArrayProp = prop.GetCustomAttributes(typeof(CustomAttribute), true);
21                     foreach (CustomAttribute attribute in oAttributeArrayProp)
22                     {
23                         attribute.Show();
24                     }
25                 }
26             }
27 
28             // 查找方法上的特性
29             foreach (var method in type.GetMethods())
30             {
31                 if (method.IsDefined(typeof(CustomAttribute), true))
32                 {
33                     object[] oAttributeArrayMethod = method.GetCustomAttributes(typeof(CustomAttribute), true);
34                     foreach (CustomAttribute attribute in oAttributeArrayMethod)
35                     {
36                         attribute.Show();
37                     }
38                 }
39             }
40 
41         }

5、实际应用

  上面说了这么多,可是我还是不会用,咋办呢。下面列出一个实际应用的小栗子。程序中,我们经常会用到枚举,通过枚举值显示文字的时候,通常会if(值){文字},下面我们用特性来获取枚举值对应的文字。

  5.1 准备一个枚举

 1         /// <summary>
 2         /// 用户状态
 3         /// </summary>
 4         public enum UserState
 5         {
 6             /// <summary>
 7             /// 正常状态
 8             /// </summary>
 9             [Remark("正常状态")]
10             Normal = 0,
11             /// <summary>
12             /// 已冻结
13             /// </summary>
14             [Remark("已冻结")]
15             Frozen = 1,
16             /// <summary>
17             /// 已删除
18             /// </summary>
19             [Remark("已删除")]
20             Deleted = 2
21         }

  5.2 显示备注

        public static string GetRemark(this Enum value)
        {
            Type type = value.GetType();
            var field = type.GetField(value.ToString());
            if (field.IsDefined(typeof(RemarkAttribute), true))
            {
                RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);
                return attribute.Remark;
            }
            else
            {
                return value.ToString();
            }
        }

  用法:

            EnumTest.UserState userState = EnumTest.UserState.Deleted;
            var remark = userState.GetRemark();
            Console.WriteLine(remark);

6、总结

    特性可以说是无处不在,EF、MVC、WCF、IOC、AOP等等都会用到特性。特性可以在不修改类的情况下对类新增一些功能。

posted @ 2019-01-24 11:23  gaozejie  阅读(359)  评论(0编辑  收藏  举报