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特性
/// <summary>
/// 会员状态
/// </summary>
public enum MemberStatus
{
/// <summary>
/// 普通会员
/// </summary>
[Remark("普通会员")]//给枚举类添加RemarkAttribute 特性
Common = 0,
/// <summary>
/// 黄金会员
/// </summary>
[Remark("黄金会员")]
Gold = 1,
/// <summary>
/// 钻石会员
/// </summary>
[Remark("钻石会员")]
Diamond = 2
}
特性使用
创建一个扩展类,用于获取枚举中特性的Remark信息
/// <summary>
/// 扩展类
/// </summary>
public static class RemarkExtend
{
/// <summary>
/// 得到枚举类型的Remark信息
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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个字符 |
特性类
建立一个抽象类作为所有验证特性的父类,便于后期使用
/// <summary>
/// 验证的抽象特性类,所有验证特性都必须继承自它
/// </summary>
public abstract class ValidateAttribute : Attribute
{
/// <summary>
/// 得到验证信息,做信息输出用
/// </summary>
/// <returns></returns>
public abstract string GetValidateInfo();
/// <summary>
/// 验证所传入的值是否符合规范
/// </summary>
/// <param name="value">所验证的值</param>
/// <returns>是否符合规范</returns>
public abstract bool Validate(object value);
}
特性类1:用于验证长度是否合法
/// <summary>
/// 验证字符串长度的特性类
/// </summary>
public class ValidateLenAttribute : ValidateAttribute
{
/// <summary>
/// 最大长度
/// </summary>
private int _Max;
/// <summary>
/// 最小长度
/// </summary>
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);
}
/// <summary>
/// 验证所传入的值是否符合规范
/// </summary>
/// <param name="value">所验证的值</param>
/// <returns>是否符合规范</returns>
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,用于验证数字是否合法
/// <summary>
/// 验证数字范围的特性类
/// </summary>
public class ValidateIntAttribute : ValidateAttribute
{
private int _Max;
private int _Min;
public ValidateIntAttribute(int min, int max)
{
this._Max = max;
this._Min = min;
}
/// <summary>
/// 验证所传入的值是否符合规范
/// </summary>
/// <param name="value">所验证的值</param>
/// <returns>是否符合规范</returns>
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
{
/// <summary>
/// 验证对象的属性是否符合规范
/// </summary>
/// <param name="obj">对象</param>
/// <param name="errList">验证失败的错误信息</param>
/// <returns>是否验证成功</returns>
public static bool Validate(this object obj, out List<string> errList)
{
Type type = obj.GetType();//获取对象的类型
errList = new List<string>();
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<string> 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<string> 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]范围内。
*/