特性提供额外的信息
编写一个示例,演示特性是怎么提供额外的信息的。
我们经常用到枚举enum
,我们常有一个需求就是我们通过枚举可以轻易获取到属性的字符串名称或者响应数字,但是一般我们做数据展示的时候不想展示属性名称而是想用一个中文描述或者其它什么的。下面我们的示例就是怎么实现这个需求。
首先我么先自定义一个特性类RemarkAttribute:
/// <summary>
/// Remark特性
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class RemarkAttribute : Attribute
{
public string Remark { get; private set; }
public RemarkAttribute(string remark)
{
this.Remark = remark;
}
}
因为我们的示例直接只作用在字段上,所以加了一个约AttributeTargets.Field
。
然后我们定义一个枚举类UserState
,在其属性上增加我们自定义的特性,我们的目标就是实现通过这个枚举的属性获取额外的信息。
/// <summary>
/// 用户状态
/// </summary>
public enum UserState
{
/// <summary>
/// 正常状态
/// </summary>
[Remark("正常状态")]
Normal = 0,
/// <summary>
/// 已冻结
/// </summary>
[Remark("已冻结")]
Frozen = 1,
/// <summary>
/// 已删除
/// </summary>
[Remark("已删除")]
Deleted = 2
}
枚举和特性都写好了,之后就是怎么得到特性上增加的信息了。我们为枚举Enum
增加一个扩展方法,通过扩展方法,获取到特性增加的额外信息。
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 = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);
return attribute.Remark;
}
else
{
return value.ToString();
}
}
}
上面的准备工作都完成了,在程序中我们直接调用。
UserState userState = UserState.Frozen;
string reamrk = userState.GetRemark();
Console.WriteLine(reamrk);
如果我们不使用特性方式,那么平时我们普通方式就是这么实现的。
if (userState == UserState.Frozen)
{
Console.WriteLine("已冻结");
}
else if (userState == UserState.Deleted)
{
Console.WriteLine("已删除");
}
对比下,发现,普通的方式只能写死判断,最害怕文字修改,而有了特性后,文字其实是直接固化在枚举上面,如果修改只需要修改枚举上面的直接就好了,用起来方便多了,代码也简洁多了,不需要写哪些无穷无尽的判断。
特性封装提供额外行为Validate验证
特性还可以提供额外的行为,我们是用数据验证来作为演示示例。
我们定一个简单的需求,比如说验证QQ的位数,我们默认QQ的位数是 大于10000 小于999999999999我们需要判断这个输入的QQ是否符合要求。
自定义一个特性LongAttribute
[AttributeUsage(AttributeTargets.Property)]
public class LongAttribute
{
private long _Min = 0;
private long _Max = 0;
public LongAttribute(long min, long max)
{
this._Min = min;
this._Max = max;
}
public bool Validate(object oValue)
{
return oValue != null
&& long.TryParse(oValue.ToString(), out long lValue)
&& lValue >= this._Min
&& lValue <= this._Max;
}
}
定义一个学习信息类StudentVip
public class StudentVip : Student
{
/// <summary>
/// QQ有个范围 大于10000 小于999999999999
/// /// </summary>
[Long(10000, 999999999999)]
public long QQ { get; set; }
}
在定义一个静态类和静态方法眼验证输入的qq是否符合规则:
public static class AttributeExtend
{
public static bool Validate<T>(this T t)
{
Type type = t.GetType();
foreach (var prop in type.GetProperties())
{
object oValue = prop.GetValue(t);
if (prop.IsDefined(typeof(LongAttribute), true))
{
LongAttribute attribute = (LongAttribute)prop.GetCustomAttribute(typeof(LongAttribute), true);
if (!attribute.Validate(oValue))
return false;
}
}
return true;
}
}
程序中调用:
StudentVip student = new StudentVip()
{
QQ = 729220650
};
if (student.Validate())
{
Console.WriteLine("特性校验成功");
}
如果不需要特性的方式,我们使用普通方式将是:
if (student.QQ > 10000 && student.QQ < 999999999999)
{
}
else
{
}
特性的优势很明显。不止上面那点优势,特性还可以校验多个属性,比如我们给StudentVip
增加一个薪资属性,在30万到100万之间:
[Long(300_000, 1_000_000)]
public long Salary { get; set; }
StudentVip student = new StudentVip()
{
QQ = 729220650,
Salary = 1010000
};
if (student.Validate())
{
Console.WriteLine("特性校验成功");
}
还可以支持多重校验,比如我们增加一个不可为空的特性:
public class RequiredAttribute
{
public bool Validate(object oValue)
{
return oValue != null
&& !string.IsNullOrWhiteSpace(oValue.ToString());
}
}
public class StudentVip : Student
{
/// <summary>
/// QQ有个范围 大于10000 小于999999999999
/// /// </summary>
[Required]
[Long(10000, 999999999999)]
public long QQ { get; set; }
[Long(300_000, 1_000_000)]
public long Salary { get; set; }
}
还需要对验证静态方法进行一下修改:
public static class AttributeExtend
{
public static bool Validate<T>(this T t)
{
Type type = t.GetType();
foreach (var prop in type.GetProperties())
{
object oValue = prop.GetValue(t);
if (prop.IsDefined(typeof(LongAttribute), true))
{
LongAttribute attribute = (LongAttribute)prop.GetCustomAttribute(typeof(LongAttribute), true);
if (!attribute.Validate(oValue))
return false;
}
//增加对不可为空特性的验证
if (prop.IsDefined(typeof(RequiredAttribute), true))
{
RequiredAttribute attribute = (RequiredAttribute)prop.GetCustomAttribute(typeof(RequiredAttribute), true);
if (!attribute.Validate(oValue))
return false;
}
}
return true;
}
}
基于以上,我们可以把代码升级优化一下,我们增加一个抽象类的特性类作为父类,定义一个抽象方法Validate
:
public abstract class AbstractValidateAttribute : Attribute
{
public abstract bool Validate(object oValue);
}
之后LongAttribute
和RequiredAttribute
类继承AbstractValidateAttribute
并重写Validate
方法:
[AttributeUsage(AttributeTargets.Property)]
public class LongAttribute : AbstractValidateAttribute
{
private long _Min = 0;
private long _Max = 0;
public LongAttribute(long min, long max)
{
this._Min = min;
this._Max = max;
}
public override bool Validate(object oValue)
{
return oValue != null
&& long.TryParse(oValue.ToString(), out long lValue)
&& lValue >= this._Min
&& lValue <= this._Max;
}
}
public class RequiredAttribute : AbstractValidateAttribute
{
public override bool Validate(object oValue)
{
return oValue != null
&& !string.IsNullOrWhiteSpace(oValue.ToString());
}
}
AttributeExtend
中验证的扩展方法就可以这么优化:
public static class AttributeExtend
{
public static bool Validate<T>(this T t)
{
Type type = t.GetType();
foreach (var prop in type.GetProperties())
{
if (prop.IsDefined(typeof(AbstractValidateAttribute), true))
{
object oValue = prop.GetValue(t);
foreach (AbstractValidateAttribute attribute in prop.GetCustomAttributes(typeof(AbstractValidateAttribute), true))
{
if (!attribute.Validate(oValue))
return false;
}
}
//object oValue = prop.GetValue(t);
//if (prop.IsDefined(typeof(LongAttribute), true))
//{
// LongAttribute attribute = (LongAttribute)prop.GetCustomAttribute(typeof(LongAttribute), true);
// if (!attribute.Validate(oValue))
// return false;
//}
//if (prop.IsDefined(typeof(RequiredAttribute), true))
//{
// RequiredAttribute attribute = (RequiredAttribute)prop.GetCustomAttribute(typeof(RequiredAttribute), true);
// if (!attribute.Validate(oValue))
// return false;
//}
}
return true;
}
}
之后无论增加多少个新定义的特性类,只要继承自AbstractValidateAttribute
就都不需要修改验证的扩展方法了。
代码优化了,是不是发现特性还可随意扩展规则而不需要修改底层代码,我们可以无限扩展数据验证规则,只要你想。
通过上面两个示例,是不是能想通了,.Net框架中我们经常使用的特性,比如model中指定哪个是主键哪个是自增,还有就是别名,数据库里面叫A,程序中叫B,怎么映射起来的。