20

C#高级编程之特性

 3 years ago
source link: http://www.cnblogs.com/shuzhongke/p/14008171.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

特性定义

MSDN的描述: 使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。  将特性与程序实体相关联后,可以在运行时使用 反射 这项技术查询特性。

参考此处作者的解释

https://www.cnblogs.com/chenxizhaolu/p/9497768.html

1.特性就是为了支持对象添加一些自我描述的信息,不影响类封装的前提添加额外信息。如果你用这个信息,那特性就有用;如果你不需要这个信息,那么这个特性就没用。

2.特性的基类:Attribute。例如:Obsolete特性,提出警告信息或错误信息,特性可以影响编译、影响运行。

3.特性类通常用Attribute结尾,在使用的时候可以用全称,也可以去掉这个结尾,也可以加上小括号显示调用构造函数,如果不加小括号默认调用无参构造函数,也可以在括号内直接给属性或字段赋值。

4.特性往往只能修饰一个对象一次,需要设置属性的属性的时候,需要给属性添加AttributeUsage属性,可以用来设置:是否允许多次修饰、修饰对象的类别(类or字段等)

5.DLL文件=IL中间语言+metadata元数据,特性信息会被编译到元数据中。我们可以通过使用ILSpy工具看到具体信息。

特性的使用

使用场景1:

这里以通过特性获取类或成员添加描述信息,然后在使用的时候拿到该信息为例:

 第一步:定义一个特性类     

    public class CustomAttribute : Attribute
    {
        public int YearInfo { get; set; }
        public CustomAttribute()
        {
            Console.WriteLine("无参数构造函数");

        }
        public CustomAttribute(int i)
        {
            YearInfo = i;
            Console.WriteLine("int 类型的构造函数");
        }
        public void Show(string typeinfo)
        {
            Console.WriteLine("********************");
            Console.WriteLine($"特性修饰的粒度{typeinfo}");
            Console.WriteLine($"所在年信息为:{YearInfo}");
            Console.WriteLine("通过反射调用特性中的方法");
        }

    }

第二步:创建特性类实例,【里面包含着验证指定粒度的Model模型(类型、属性、方法)需要的数据,后面数据验证场景会讲到】

此处只是Model的不同粒度上附加数据。这里分别将特性附加于类型、属性和方法上。

    public class Student
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public void Answer(string message)
        {
            Console.WriteLine(message);
        }
    }
    [Custom(2018)]
    class StudentVIP:Student
    {
        [Custom(2019)]
        public string VIPGroup { get; set; }
        [Custom(2020)]
        public void HomeWork()
        {
            Console.WriteLine("Homework");
        }
        public long Salary { get; set; }
    }

通过ILSpy反编译工具可以看到:标记了特性的元素,都会在元素内部生成一个.custom,但是C#不能在元素内部调用

.class private auto ansi beforefieldinit LearningAttribute.StudentVIP
    extends LearningAttribute.Student
{
    .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = (
        01 00 e2 07 00 00 00 00
    )
    // Fields
    .field private string '<VIPGroup>k__BackingField'
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = (
        01 00 00 00 00 00 00 00
    )
    .field private int64 '<Salary>k__BackingField'
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = (
        01 00 00 00 00 00 00 00
    )

    // Methods
    .method public hidebysig specialname 
        instance string get_VIPGroup () cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x2441
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldfld string LearningAttribute.StudentVIP::'<VIPGroup>k__BackingField'
        IL_0006: ret
    } // end of method StudentVIP::get_VIPGroup

    .method public hidebysig specialname 
        instance void set_VIPGroup (
            string 'value'
        ) cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x2449
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: stfld string LearningAttribute.StudentVIP::'<VIPGroup>k__BackingField'
        IL_0007: ret
    } // end of method StudentVIP::set_VIPGroup

    .method public hidebysig 
        instance void HomeWork () cil managed 
    {
        .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = (
            01 00 e4 07 00 00 00 00
        )
        // Method begins at RVA 0x2452
        // Code size 13 (0xd)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldstr "Homework"
        IL_0006: call void [mscorlib]System.Console::WriteLine(string)
        IL_000b: nop
        IL_000c: ret
    } // end of method StudentVIP::HomeWork

    .method public hidebysig specialname 
        instance int64 get_Salary () cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x2460
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldfld int64 LearningAttribute.StudentVIP::'<Salary>k__BackingField'
        IL_0006: ret
    } // end of method StudentVIP::get_Salary

    .method public hidebysig specialname 
        instance void set_Salary (
            int64 'value'
        ) cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x2468
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: stfld int64 LearningAttribute.StudentVIP::'<Salary>k__BackingField'
        IL_0007: ret
    } // end of method StudentVIP::set_Salary

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x2471
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void LearningAttribute.Student::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method StudentVIP::.ctor

    // Properties
    .property instance string VIPGroup()
    {
        .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = (
            01 00 e3 07 00 00 00 00
        )
        .get instance string LearningAttribute.StudentVIP::get_VIPGroup()
        .set instance void LearningAttribute.StudentVIP::set_VIPGroup(string)
    }
    .property instance int64 Salary()
    {
        .get instance int64 LearningAttribute.StudentVIP::get_Salary()
        .set instance void LearningAttribute.StudentVIP::set_Salary(int64)
    }

} 

第三步:使用特性类实例。

将特性与程序实体相关联后,利用反射来获取附加在这些Model上的数据。一般是传入这个实体,然后利用反射判断其是否贴有数据,如果有,然后调用object[] aAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);拿到这个特性的对象(其在这个环节进行了实例化),同时此时type又包含具体的数据信息,就可以将特性的附加信息与type进行互操作了。

                Student student = new Student();
                StudentVIP studentVIP = new StudentVIP()
                {
                    Id = "1",
                    Name = "HAHAH",
                    VIPGroup = "Super学员"

                };
                InvokeCenter.ManagerStudent<Student>(studentVIP);
    public class InvokeCenter
    {
        public static void ManagerStudent<T>(T student) where T : Student
        {
            Console.WriteLine(student.Id);
            Console.WriteLine(student.Name);
            student.Answer("LNW");

            Type type = student.GetType();
            //这个type类级别上标注了特性
            if (type.IsDefined(typeof(CustomAttribute), true))//先判断
            {
                object[] aAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);//此处为type
                foreach (CustomAttribute item in aAttributeArray)
                {
                    item.Show(type.Name);
                
                }

            }
            //这个type属性上标注了特性
            foreach (var prop in type.GetProperties())//先判断
            {
                if (prop.IsDefined(typeof(CustomAttribute), true))
                {
                    object[] aAttributeArray = prop.GetCustomAttributes(typeof(CustomAttribute), true);//此处为prop
                    foreach (CustomAttribute item in aAttributeArray)
                    {
                        item.Show(prop.Name);
                    }
                }
            }
            //同理,方法上也可以获取
            foreach (var method in type.GetMethods())
            {
                if (method.IsDefined(typeof(CustomAttribute), true))
                {
                    object[] aMethodArray = method.GetCustomAttributes(typeof(CustomAttribute), true);//此处为method
                    foreach (CustomAttribute item in aMethodArray)
                    {
                        item.Show(method.Name);
                    }
                }
            }
        }
    }

结果为:

EzeeUvV.png!mobile

使用场景2

添加说明信息并获取,方便进行拓展。

比如我想根据不同的枚举状态显示不同的字符串信息。

定义枚举:

    public enum UserStatus
    {
        /// <summary>
        /// 正常状态
        /// </summary>
        Normal=0,
        /// <summary>
        /// 冻结状态
        /// </summary>
        Frozen =1,
        /// <summary>
        /// 删除状态
        /// </summary>
        Delted = 2

    }

常规判断操作:

                UserStatus userStatus = UserStatus.Normal;
                if (userStatus == UserStatus.Normal)
                {
                    Console.WriteLine("正常状态");
                }
                else if (userStatus == UserStatus.Frozen)
                {
                    Console.WriteLine("冻结状态");
                }
                else
                {
                    Console.WriteLine("已删除");
                }
//.....如果发生文字修改,那么改动量特别大,if else if 分支特别长。。

使用特性

    public enum UserStatus
    {
        /// <summary>
        /// 正常状态
        /// </summary>
        [Remark("正常状态")]
        Normal=0,
        /// <summary>
        /// 冻结状态
        /// </summary>
        [Remark("冻结状态")]
        Frozen =1,
        /// <summary>
        /// 删除状态
        /// </summary>
        [Remark("删除状态")]
        Delted = 2
//可以自由拓展。


    }
//拓展方法
                string remark3withExtendMethod = UserStatus.Delted.GetRemark();
public static class AttributeExtend
	{
 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 = field.GetCustomAttribute(typeof(RemarkAttribute), true) as RemarkAttribute;
                return attribute.Remak;
            }
            else
            {
                return value.ToString();
            }
        }
}
//其中GetFiled API含义如下:


// 摘要:
        //     搜索具有指定名称的公共字段。
        //
        // 参数:
        //   name:
        //     包含要获取的数据字段的名称的字符串。
        //
        // 返回结果:
        //     如找到,则为表示具有指定名称的公共字段的对象;否则为 null。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     name 为 null。
        //
        //   T:System.NotSupportedException:
        //     此 System.Type 对象是尚未调用其 System.Reflection.Emit.TypeBuilder.CreateType 方法的 System.Reflection.Emit.TypeBuilder。
        public FieldInfo GetField(string name);

使用场景3

做数据验证。

public class IntValidateAttribute : Attribute//特性名称约定俗成是以Attribute结尾,特性命名以具体功能名称为命名,此处就是整型数据验证
    {
        /// <summary>
        /// 最小值
        /// </summary>
        private int minValue { get; set; }
        /// <summary>
        /// 最大值
        /// </summary>
        private int maxValue { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="minValue"></param>
        /// <param name="maxValue"></param>
        public IntValidateAttribute(int minValue, int maxValue)
        {
            this.minValue = minValue;
            this.maxValue = maxValue;
        }
        /// <summary>
        /// 检验值是否合法
        /// </summary>
        /// <param name="checkValue"></param>
        /// <returns></returns>
        public bool Validate(int checkValue)
        {
            return checkValue >= minValue && checkValue <= maxValue;
        }
    }

     public class User
    {
        [IntValidate(1, 10)]
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class BaseDal
    {
        public static string Insert<T>(T model)
        {
            Type modelType = typeof(T);//Model模型
            //获取类型的所有属性
            PropertyInfo[] propertyInfos = modelType.GetProperties();
            
            bool boIsCheck = true;
            //循环所有属性
            foreach (var property in propertyInfos)
            {
                //获取属性的所有特性
                object[] attrs = property.GetCustomAttributes(true);
                if (property.PropertyType.Name.ToLower().Contains("int"))
                {
                    foreach (var attr in attrs)
                    {
                        if (attr is IntValidateAttribute)
                        {
                            IntValidateAttribute intValidate = (IntValidateAttribute)attr;//拿到追加在Model上的特性实例化对象
                            //执行特性的验证逻辑
                            boIsCheck = intValidate.Validate((int)property.GetValue(model));//特性实例化对象intValidate执行对象方法Validate,同时proprty.GetValue(model)获取此时传进来的model实体的属性数据。进行数据验证。
                        }
                    }
                }
                if (!boIsCheck)
                {
                    break;
                }
            }
            if (boIsCheck)
            {
                return "验证通过,插入数据库成功";
            }
            else
            {
                return "验证失败";
            }
        }
    }
     class Program
    {
        public static void Main(string[] args)
        {
            string msg = BaseDal.Insert<User>(new User() { Id = 123, Name = "lvcc" });//传入Model User和User实体对象new User() { Id = 123, Name = "lvcc" }
            Console.WriteLine(msg);
        }
    }

总结:

使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。  将特性与程序实体相关联后,可以在运行时使用 反射 这项技术查询特性。 

特性具有以下属性:

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

同时在MVC---EF--WCF--IOC 都有使用特性,无处不在。下篇就以简单的ORM模型来讲解反射与特性的简单使用。

参考资料:

https://www.cnblogs.com/chenxizhaolu/p/9497768.html

https://www.cnblogs.com/woadmin/p/9406970.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK