首页 > 其他分享 >特性(Attribute)详解

特性(Attribute)详解

时间:2023-01-13 23:23:15浏览次数:39  
标签:attribute Name 标记 Attribute 特性 详解 ._ public

前言:本文中有使用到扩展方法,可以参考博主关于扩展方法的笔记       https://www.cnblogs.com/JohnTang/p/10945696.html 

特性的本质就是一个类,声明的时候,默认以Attribute结尾,直接或者是间接继承在Attribute抽象类。

 如何使用特性:把这个特性以[]包裹标记在类或者是类内部的成员上,使用特性(不管是系统自带的特性,还是自定义特性,其实都是调用特性类的构造方法)

特性和注释的区别---看看特性的本质
1.注释只是会个描述;在编译后,是不存在的
2.特性呢?编译后是存在的

特性的应用场景:  MVC 、webapi、IOC、ORM......可以说是无处不在

 

如何自定义特性,特性的多种标记

1.标记的特性,如果是以Attribute结尾,在标记时Attribute可以省略掉
2.可以标记的类内部的任何成员上
3.特性在标记的时候,其实就是去调用构造函数
4.在标记的时候也可以对公开的属性或者字段赋值
5.特性标记默认是不能重复标记的
6.AttributeUsage:也是一个特性,是用来修饰特性的特性--约束特性的特性
7.AttributeTargets指定当前特性只能标记在某个地方;建议大家在自己定义特性的时候,最好能够明确指定AttributeTargets--明确告诉别人,我这个特性是专们用来标记在哪里的
8.AllowMultiple:是否可以重复标记
9.Inherited:是否可以继承特性

创建自定义特性,并标记 示例:

1 创建自定义特性

    /// <summary>
    /// 不管是自定义特性、还是系统中的特性
    /// 都必须直接或间接继承自Attribute抽象类
    /// </summary>
    public class CustomAttribute :Attribute
    {

        private int _Id { get; set; }
        public string _Name { get; set; }

        public int _Age;


        public CustomAttribute(int id)
        {
            this._Id = id;
        }

        public CustomAttribute(string name)
        {
            this._Name = name;
        }

        public void Do()
        {
            Console.WriteLine("this is  CustomAttribute");
        }
    }

 

2.标记特性

   //自定义特性标记
        /// <summary>
        /// 自定义特性标记
        /// 特性就是调用特性类的构造方法
        /// </summary>
        [Custom(112)]
        public int Id { get; set; }


        [Custom("茅斯李")]
        public string Name { get; set; }

疑问点,我们如果用student.custom是调用不到的,如何调用到特性内部的成员--自定义的这个特性,如何才能使用他呢?

1.进行反编译之后,在标记有特性的类的内部,生成了cutom的成员,但是我们不能直接调用;---要通过反射来调用的;要让特性生效,实际上来说是要去执行我们这个特性?---就需要构造特性的实例

特性标记后好像无法直接取调用他;好像没什么用;---当然是要用反射的,系统提供的特性可以使用是因为在封装好的底层代码里进行了处理,但是由于自定义的特性,系统无法知道需要什么样的规则

所以需要我们自己来指定规则。
2.在使用反射获取特性的时候,可以把标记在任何地方的特性都可以获取到
3.既然标记的特性可以通过反射来获取到实例,就可以加以应用

 

        /// <summary>
        /// 通过反射来调用特性
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void Show(Student student)
        {
            Type type = student.GetType();
        
            var  tt= (type.GetCustomAttributes(true)).GetType().Name;
          
            //获取当前Type下所有的属性上标记的特性
            foreach (PropertyInfo prop in type.GetProperties())
            {
                if (prop.IsDefined(typeof(CustomAttribute), true))
                {
                    //2.获取--先判断再获取--为了提高性能
                    foreach (CustomAttribute attribute in prop.GetCustomAttributes(true))
                    {
                        Console.WriteLine($"attribute._Name:{attribute._Name}");
                        Console.WriteLine($"attribute._Age:{attribute._Age}");
                        attribute.Do();
                    }
                }
            }

            //获取当前Type下所有的字段上标记的特性
            foreach (FieldInfo field in type.GetFields())
            {
                if (field.IsDefined(typeof(CustomAttribute), true))
                {
                    //2.获取--先判断再获取--为了提高性能
                    foreach (CustomAttribute attribute in field.GetCustomAttributes(true))
                    {
                        Console.WriteLine($"attribute._Name:{attribute._Name}");
                        Console.WriteLine($"attribute._Age:{attribute._Age}");
                        attribute.Do();
                    }
                }
            }

            //获取当前Type下所有的方法上标记的特性
            foreach (MethodInfo method in type.GetMethods())
            {
                foreach (ParameterInfo para in method.GetParameters())
                {
                    if (para.IsDefined(typeof(CustomAttribute), true))
                    {
                        //2.获取--先判断再获取--为了提高性能
                        foreach (CustomAttribute attribute in para.GetCustomAttributes(true))
                        {
                            Console.WriteLine($"attribute._Name:{attribute._Name}");
                            Console.WriteLine($"attribute._Age:{attribute._Age}");
                            attribute.Do();
                        }
                    }
                }


                if (method.IsDefined(typeof(CustomAttribute), true))
            {
               // 2.获取--先判断再获取--为了提高性能
                foreach (CustomAttribute attribute in method.GetCustomAttributes(true))
                {
                    Console.WriteLine($"attribute._Name:{attribute._Name}");
                    Console.WriteLine($"attribute._Age:{attribute._Age}");
                    attribute.Do();
                }
            }
        }



    }
    }

通过上述代码,我们可以获取到指定类上标记的所有特性(包含方法、字段、属性),那么我们会思考即使拿到了想要的信息,到底能给我们带来什么样的好处呢

场景:

比方说有一个用户信息,用户信息中有一个状态字段;在数据库中保存数据的时候,保存的是1,2,3 对应的就是这个枚举;数据库中保存的是数字,但是我们在查询到数据以后,展示给界面的时候,需要展示一个文字描述。

                    if (userState == UserStateEnum.Normal)
                    {
                        Console.WriteLine("正常状态");
                    }
                    else if (userState == UserStateEnum.Frozen)
                    {
                        Console.WriteLine("已冻结");
                    }

这样的判断分支太多,而且如果枚举类发生更改,调用这个枚举类的所有类都需要更改,这样肯定不行。

我们可以通过特性获取描述信息信息----获取额外信息,

好处:
a.如果增加字段,就可以直接获取不用其他的改动
b.描述修改后,获取描述信息的方法不用修改

具体实现: 通过反射+特性+扩展方法,可以封装一个获取额外新的的公共方法

代码示例如下:

枚举

    /// <summary>
    /// 比方说有一个用户信息,用户信息中有一个状态字段;在数据库中保存数据的时候,保存的是1,2,3  对应的就是这个枚举;数据库中保存的是数字,但是我们在查询到数据以后,展示给界面的时候,能展示数据吗?---需要展示一个文字描述
    /// </summary>
    //[RemarkAttribute]
    public enum UserStateEnum
    {
        /// <summary>
        /// 正常状态---改成 “正常”
        /// </summary>
        [Remark("正常状态")]
        Normal = 1,
        /// <summary>
        /// 已冻结
        /// </summary>
        [Remark("已冻结")]
        Frozen = 2,

        /// <summary>
        /// 已删除
        /// </summary>
        [Remark("已删除")]
        Deleted = 3,


        [Remark("其他")]
        Other = 4,


        [Remark("Other1Other1Other1Other1Other1Other1Other1")]
        Other1 = 5
    }

自定义特性类

    public class RemarkAttribute :Attribute
    {
        private string? _Description;
        public  RemarkAttribute(string description)
        {
            this._Description = description;
        }

        public string GetRemark() 
        {
            return this._Description;
        }
    }

 

扩展方法

        public static string GetRemark(this Enum @enum)
        {
            Type type = @enum.GetType();
            string filedName = @enum.ToString();
            FieldInfo fieldInfo = type.GetField(filedName);
            if (fieldInfo.IsDefined(typeof(RemarkAttribute),true))
            {
                RemarkAttribute attribute = fieldInfo.GetCustomAttribute<RemarkAttribute>();
                string description = attribute.GetRemark();
                return description;
            }
            return @enum.ToString();
        }

UserStateEnum normal = UserStateEnum.Normal;
string strnormalRemark = RemarkAttributeExtension.GetRemark(normal);

完成调用,这样,不管枚举里如何修改,对于调用方而言都不用做任何更改

需求进一步升级: 

1.如果要保存一条数据到数据库中去
2.从前端提交过来的数据格式为:
{
"id": 0,
"name": "string",
"age": 0,
"state": 1
}
3.包含了很多字;
4.如果数据库中Name的值要求存储的长度为40个字符--如果保存的数据超过40个字符---肯定会报错
5.肯定要在保存之前就需要验证这行数据

//模拟数据
UserInfo adduse = new UserInfo()
{
    Id = 123,
    Name = "456464",
    Age = 25,
    Mobile = "sdfsdf"
};
//传统方式如下,缺点:判断分支太多,代码量很大
if (adduse.Name == null)
{
    Console.WriteLine("不能为空");
}
if (adduse.Name.Length > 40)
{
    Console.WriteLine("Name超长了");
}
if (adduse.Mobile.Length != 11)
{
    Console.WriteLine("手机号有问题");
}

 验证思路概述:

增加了一个特性:可以对一个实体中的字段做验证 - 和验证不能为空
1.通过特性加反射额外的获取了一个功能
2.实体验证 == 特性获取额外信息 + 特性获取额外的来完成的

好处:
1.只要是把验证的规则特性定义好,就可以重新使用
2.如果需要验证哪个属性,就把特性标记在哪个属性上就可以了;
3.只是标记了一个特性,就可以获取了一个验证的逻辑

 

具体实现: 

1.创建一个抽象类 继承 Attribute,定义用于验证的抽象方法
2.创建一个验证类,继承自抽象类,重写用于验证的抽象方法,并定义公开的验证返回信息
3创建反射验证类(静态类),这里是用于最上层调用验证,通过反射获取到信息后调用验证类里的验证方法,并将验证的值返回。

抽象类Code

    public abstract class AbstractAttribute : Attribute
    {

        public abstract ApiResult Validate(object oValue);

    }

 

验证类Code

    /// <summary>
    /// AbstractAttribute 中继承了Attribute
    /// </summary>
    public class RequiredTestAttribute : AbstractAttribute
    {
        public string _ErrorMessage;


        public RequiredTestAttribute(string errorMessage) 
        {
            this._ErrorMessage = errorMessage;
        }


        /// <summary>
        /// 重写AbstractAttribute类的抽象方法
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public override ApiResult Validate(object value)
        {
            //if (string.IsNullOrWhiteSpace(value)
            //{
            //    return false;
            //}
            //else
            //{
            //    return true;
            //} 
            bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
            if (bResult)
            {
                return new ApiResult()
                {
                    Success = bResult
                };
            }
            else
            {
                return new ApiResult()
                {
                    Success = bResult,
                    ErrorMessage = _ErrorMessage
                };
            }

        }
    }

 

反射验证类(供上端调用)Code

        public static ApiResult ValiDate<T>(this T t)
        {
            Type type = t.GetType();
            //遍历传递过来的类型的属性
            foreach (PropertyInfo prp in type.GetProperties())
            {
                //判断属性上是否有RequiredTestAttribute
                if (prp.IsDefined(typeof(RequiredTestAttribute), true))
                {
                    RequiredTestAttribute attribute = prp.GetCustomAttribute<RequiredTestAttribute>();
                    object ovalue = prp.GetValue(t);
                    ApiResult result = attribute.Validate(ovalue);
                    if (result.Success == false)
                    {
                        return result;
                    }

                }
            }
            return new ApiResult() { Success = true };

        }

 

标签:attribute,Name,标记,Attribute,特性,详解,._,public
From: https://www.cnblogs.com/JohnTang/p/17048278.html

相关文章