C# 特性 (Attribute)


C# 特性(Attribute)


1.语法

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。

说白了就是没有破坏类型封装的前提下,可以加点额外的信息和行为。

声明

实际上特性就是一个类,继承或者间接继承自Attribute
如我们经常使用到的SerializableAttribute特性,就是继承自Attribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)]
[ComVisible(true)]
public sealed class SerializableAttribute : Attribute//继承自Attribute 
{
    public SerializableAttribute();
}

当然我们也是可以自定义一个特性的,它就像一个普通的类一样,有着自己的构造函数、属性、方法、字段等。

在定义特性时,特性的名词我们一般是以Attribute结尾的。
如下代码:

public class CustomAttribute : Attribute
{

    //无参数构造函数
    public CustomAttribute()
    {

    }

    //带有参数的构造函数
    public CustomAttribute(int id)
    {

    }

    //属性
    public string Name { get; set; }

    //字段
    public string Remark = "";
}

在使用特性时,只要在使用的元素上面加上[特性名称]即可。

注意:

  • 每个元素可以添加多个特性
  • 如果特性时以Attribute结尾的话,是可以省略Attribute的
  • 特性可以添加参数

如下代码

[Serializable]
[Custom]
[CustomAttribute]
[Custom()]//同 [Custom]
[Custom(1)]//带有参数的构造函数
[Custom(1, Name = "Oliver", Motto = "自律给我自由")]//直接指定成员
public class Student
{
    [Custom]//给属性添加特性
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    [Custom]//给方法添加特性
    [return:Custom]//给方法的返回值添加特性
    public string Study([Custom]string content)//给方法的参数添加特性
    {
        return "我正在学习" + content;
    }
}

约束

使用AttributeUsage 特性可以对自定义的特性进行约束,如:可以约束只能将特性添加到属性上等等,具体如下表所示:

AttributeTargets 描述
Assembly 程序集
Module 模块
Class
Struct 结构;即,类型值
Enum 枚举
Constructor 构造函数
Method 方法
Property 属性
Field 字段
Event 事件
Interface 接口
Parameter 参数
Delegate 委托
ReturnValue 返回的值
GenericParameter 泛型参数
All 所有元素

使用方式直接在特性类上面添加AttributeUsage特性。代码如下:

//AttributeTargets:表示该特性可以应用于哪些程序元素的值。
//AllowMultiple:该值指示是否可以为一个程序元素指定多个实例所指示的特性。
//Inherited:该值确定指示的属性是否由派生类和重写成员继承。
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class CustomAttribute : Attribute

2.使用方法

基本的语法我们都懂了,但是如何使用它呢?情看以下代码:

使用类的特性

Type studentType = typeof(Student);
if (studentType.IsDefined(typeof(CustomAttribute), true))//判断该Type是否存在指定的特性
{
    //获取到类的特性,然后可以根据自己的需求做一下应用
    CustomAttribute attributeType = (CustomAttribute)(studentType.GetCustomAttributes(typeof(CustomAttribute), true)[0]);
    Console.WriteLine($"attributeType.Name={attributeType.Name}\r\nattributeType.Motto={attributeType.Motto}");
}
//对应方法、字段、属性的特性的使用基本一致。

可能有的人看了上面的代码一头雾水,使用自己定义的特性为何这么麻烦呢,还需要用到反射。见系统定义的特性直接拿来就用,不用做这么麻烦的事儿。在这里我说明下,只有是特性就需要有调用的地方,只是系统中的特性被封装了,而不用我们做这么麻烦的操作。

下面就举两个例子来说明下特性:

应用场景一

场景:我们在开发过程中经常使用到枚举,然后再前台做相应的列表框,给用户显示相应的文字,然后再代码中使用枚举。

可能我们给用户显示的是黄金会员,但是在代码中我们会使用MemberStatus.Gold,在前台页面显示时,我们难免会使用大量的if语句进行判断。在这里我们就可以使用特性解决这个问题。

特性类

我们定义一个特性类,专门用作存储枚举的备注信息。

[AttributeUsage(AttributeTargets.Field)]//表示该特性只适用于字段(枚举中的项实际上是字段)
public class RemarkAttribute : Attribute
{
    private string _Remark = "";
    public RemarkAttribute(string remark)//构造函数
    {
        this._Remark = remark;
    }

    public string GetRemark()
    {
        return this._Remark;
    }
}
枚举类

定义枚举,并在枚举的每个项上添加 RemarkAttribute特性

/// 
/// 会员状态
/// 
public enum MemberStatus
{
    /// 
    /// 普通会员
    /// 
    [Remark("普通会员")]//给枚举类添加RemarkAttribute 特性
    Common = 0,
    /// 
    /// 黄金会员
    /// 
    [Remark("黄金会员")]
    Gold = 1,
    /// 
    /// 钻石会员
    /// 
    [Remark("钻石会员")]
    Diamond = 2
}
特性使用

创建一个扩展类,用于获取枚举中特性的Remark信息

/// 
/// 扩展类
/// 
public static class RemarkExtend
{
    /// 
    /// 得到枚举类型的Remark信息
    /// 
    /// 
    /// 
    public static string GetRemark(this Enum value)
    {
        Type type = value.GetType();
        FieldInfo fieldInfo= type.GetField(value.ToString());
        if (fieldInfo.IsDefined(typeof(RemarkAttribute), true))
        {
            RemarkAttribute remarkAttrbute = (RemarkAttribute)(fieldInfo.GetCustomAttributes(typeof(RemarkAttribute), true)[0]);
            return remarkAttrbute.GetRemark();
        }
        return value.ToString();
    }
}
调用方式

直接调用枚举的扩展方法,从而得到枚举的备注信息

Console.WriteLine(MemberStatus.Common.GetRemark());
Console.WriteLine(MemberStatus.Gold.GetRemark());
Console.WriteLine(MemberStatus.Diamond.GetRemark());

/*
输出:
普通会员
黄金会员
钻石会员
*/

应用场景二

再举一个验证数据的例子。

在我们开发的过程中经常需要把用户的输入的值,存入数据库,在存入数据库之前需要做相应的检查,这个问题我们也可以使用特性解决。

比如我们有一个用户信息表如下:

项目 需要验证
姓名 长度为2~10个字符
年龄 1~100
展示信息 长度不超过100个字符
特性类

建立一个抽象类作为所有验证特性的父类,便于后期使用

/// 
/// 验证的抽象特性类,所有验证特性都必须继承自它
/// 
public abstract class ValidateAttribute : Attribute
{
    /// 
    /// 得到验证信息,做信息输出用
    /// 
    /// 
    public abstract string GetValidateInfo();
    /// 
    /// 验证所传入的值是否符合规范
    /// 
    /// 所验证的值
    /// 是否符合规范
    public abstract bool Validate(object value);
}

特性类1:用于验证长度是否合法

/// 
/// 验证字符串长度的特性类
/// 
public class ValidateLenAttribute : ValidateAttribute
{
    /// 
    /// 最大长度
    /// 
    private int _Max;
    /// 
    /// 最小长度
    /// 
    private int _Min;
    public ValidateLenAttribute(int min, int max)
    {
        this._Max = max;
        this._Min = min;
    }

    public override string GetValidateInfo()
    {
        return string.Format("字符长度必须在[{0}-{1}]范围内。", this._Min, this._Max);
    }

    /// 
    /// 验证所传入的值是否符合规范
    /// 
    /// 所验证的值
    /// 是否符合规范
    public override bool Validate(object value)
    {
        if (value != null)
        {
            int len = value.ToString().Length;
            if (len <= this._Max && len >= this._Min)
            {
                return true;
            }
        }
        return false;
    }
}

特性类2,用于验证数字是否合法

/// 
/// 验证数字范围的特性类
/// 
public class ValidateIntAttribute : ValidateAttribute
{
    private int _Max;
    private int _Min;
    public ValidateIntAttribute(int min, int max)
    {
        this._Max = max;
        this._Min = min;
    }

    /// 
    /// 验证所传入的值是否符合规范
    /// 
    /// 所验证的值
    /// 是否符合规范
    public override bool Validate(object value)
    {
        if (!string.IsNullOrEmpty(value.ToString()) && !string.IsNullOrWhiteSpace(value.ToString()))
        {
            if (int.TryParse(value.ToString(), out int result))
            {
                if (result <= this._Max && result >= this._Min)
                {
                    return true;
                }
            }
        }
        return false;
    }

    public override string GetValidateInfo()
    {
        return string.Format("数字必须在[{0}-{1}]范围内。", this._Min, this._Max);
    }
}

特性使用

通过扩展方法使用特性类

public static class ValidateExtend
{

    /// 
    /// 验证对象的属性是否符合规范
    /// 
    /// 对象
    /// 验证失败的错误信息
    /// 是否验证成功
    public static bool Validate(this object obj, out List errList)
    {
        Type type = obj.GetType();//获取对象的类型

        errList = new List();

        bool flag = true;
        foreach (PropertyInfo property in type.GetProperties())//得到该类型的所有属性,并循环
        {
            if (property.IsDefined(typeof(ValidateAttribute), true))//判断该属性是否 使用ValidateAttribute 特性
            {
                object value = property.GetValue(obj);//得到该属性的值
                foreach (ValidateAttribute validateAttribute in property.GetCustomAttributes(typeof(ValidateAttribute), true))//得到该属性所有的ValidateAttribute特性
                {
                    if (!validateAttribute.Validate(value))//逐一言之是否符合规范
                    {
                        errList.Add(property.Name + "="+ value.ToString() + ",不满足验证条件:" + validateAttribute.GetValidateInfo());//拼接错误信息
                        flag = false;
                    }
                }
            }
        }
        return flag;
    }
}
用户类

在用户类中直接添加特性就可以完成验证,可以为一个属性添加多个验证特性,这样就可以双重验证(比如即验证数字范围,又验证字符串长度...)

public class Member
{
    [ValidateLenAttribute(2, 10)]
    public string Name { get; set; }
    [ValidateIntAttribute(1, 100)]
    public int Age { get; set; }
    [ValidateLenAttribute(0, 100)]
    public string Display { get; set; }
}
调用

调用时很简单

Member member1 = new Member() { Age = 18, Display = "自律给我自由", Name = "Oliver" };
Console.WriteLine("member1验证结果:");
if (!member1.Validate(out List errList))
{
    Console.WriteLine("验证失败:\r\n\t" + string.Join("\r\n\t", errList.ToArray()));
}
else
{
    Console.WriteLine("验证通过");
}
Member member2 = new Member() { Age = 188, Display = "自律给我自由", Name = "OliverOliver" };
Console.WriteLine("member2验证结果:");

if (!member2.Validate(out List errList2))
{
    Console.WriteLine("验证失败:\r\n\t" + string.Join("\r\n\t", errList2.ToArray()));
}
else
{
    Console.WriteLine("验证通过");
}

/* 
 输出:
    member1验证结果:
    验证通过
    member2验证结果:
    验证失败:
            Name=OliverOliver,不满足验证条件:字符长度必须在[2-10]范围内。
            Age=188,不满足验证条件:数字必须在[1-100]范围内。
 */