目录
一、概述
特性,说的白话一点,就是说明某个东西的特殊性,它不能单独存在,必须依附在某个东西上,这里的东西是指C#代码中类,属性,方法。特性就是对这些类,方法,属性进一步修饰。比如说,定义个 sting 类型 属性A ,我们要求该属性最小长度为5.这个要求就可以说是它的特性。
二、基本概念
特性:一个继承于Attribute的特殊类,以“[attribut()]”的方式依附在属性,方法,类等其它 Type类型上,从而对其进行约束,如下:
[Required]
public string EmpNo { get; set; }
其中[Required] 就是对EmpNo属性修饰,约束EmpNo不能为null,否则参数校验就会出错。
三、自定义特性
3.1 定义特性无参构造函数&使用
//[AttributeUsage(AttributeTargets.All)] //表示对该特性运用地方没有限制
//[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)] //表示该特性只能运用到Property和Field
//[AttributeUsage(AttributeTargets.All,AllowMultiple = true)] //AllowMultiple = true 表示该特性在同一对象上多次引用
[AttributeUsage(AttributeTargets.Property)] //表示该特性只能运用到Property
public class MaxLengthValidateAttribute: Attribute
{
}
public class User
{
[MaxLengthValidate()] //应用自定义Attribute
public string EmpNo { get; set; }
}
3.2 自定义带参构造函数特性&使用&给内部属性【ErrMsg】赋值
[AttributeUsage(AttributeTargets.Property)]
public class MaxLengthValidateAttribute: Attribute
{
private int _Max { get; set; };
public string ErrMsg { get; set; };
public MaxLengthValidateAttribute(int max)
{
_Max = max;
}
}
public class User
{
[MaxLengthValidate(max:5,ErrMsg ="EmpNo 最大长度为5")] //调用有参构造函数,并给属性ErrMsg赋值
public string EmpNo { get; set; }
}
注意事项:需要多个属性值赋值,用“,”号隔开
四、给对象的属性自定义验证规则
场景设置:现在我们需要设置某些string类型的长度,Int类型的大小范围。若我们创建的类的实例如果属性不满足要求就报错
1. 创建一个验证抽象 Atrribute=>【MyValidateAtrribute】(抽象出来的好处将在后面解析)
/// <summary>
/// 定义一个抽象Atrribute
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public abstract class MyValidateAtrribute:Attribute
{
/// <summary>
/// 记录验证失败信息
/// </summary>
public abstract string ErrMsg { get; set; }
/// <summary>
/// 定义验证规则
/// </summary>
/// <param name="oValue"></param>
/// <returns></returns>
public abstract bool Validate(object oValue);
}
2. 创建两个继承MyValidateAtrribute 的Atrribute,分别为
StringLengthAtrribute => 验证String属性最大长度和最小长度
IntRangAttribute => 验证Int类型大小
/// <summary>
/// 验证String属性最大长度和最小长度
/// </summary>
public class StringLengthAtrribute : MyValidateAtrribute
{
/// <summary>
/// 最大长度
/// </summary>
public long Max { get; set; } = 9999;
/// <summary>
/// 最长度
/// </summary>
public long Min { get; set; } = 0;
public override string ErrMsg { get; set; }
public override bool Validate(object oValue)
{
return oValue.ToString().Length >= Min && oValue.ToString().Length <= Max;
}
}
/// <summary>
/// 验证Int类型大小
/// </summary>
public class IntRangAttribute : MyValidateAtrribute
{
/// <summary>
/// long最大值
/// </summary>
public long Max { get; set; }
/// <summary>
/// long最小值
/// </summary>
public long Min { get; set; }
public override string ErrMsg { get; set; }
public override bool Validate(object oValue)
{
if (long.TryParse(oValue.ToString(), out long longValue))
{
if (longValue >= Min && longValue <= Max)
{
return true;
}
else
{
return false;
}
} else
{
ErrMsg = "该参数不支持IntRang属性验证!";
return false;
}
}
}
3. 创建一个记录验证失败结果的类
/// <summary>
/// 记录验证失败结果类
/// </summary>
public class ValidateErrResult
{
/// <summary>
/// 属性名称
/// </summary>
public string PropName { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string ErrMsg { get; set; }
}
4. 通过泛型创建一个class的属性验证方法
/// <summary>
/// 针对类属性验证
/// </summary>
public class MyPropValidate
{
/// <summary>
/// 对class 属性进行验证合法性
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static List<ValidateErrResult> Validate<T>(T obj)
where T:class
{
var reValidateErr = new List<ValidateErrResult>(); //定一个验证失败结果集
var type = typeof(T);
var props = type.GetProperties()
.Where(prop => prop.IsDefined(typeof(MyValidateAtrribute), true)); //获取包含 MyValidateAtrribute 的特性
foreach (var prop in props)
{
var value = prop.GetValue(obj); //获取该属性的值
foreach (MyValidateAtrribute attribute in prop.GetCustomAttributes(typeof(MyValidateAtrribute), true)) //获取该属性所有 MyValidateAtrribute 的特性
{
if (!attribute.Validate(value)) //验证失败
{
reValidateErr.Add(new ValidateErrResult() { PropName = prop.Name, ErrMsg = attribute.ErrMsg });
}
}
}
return reValidateErr;
}
}
注意事项:
该处直接去找有【MyValidateAtrribute】特性的属性,避免了单独分别处理【StringLengthAtrribute】 和【IntRangAttribute】,应为他们都继承于【MyValidateAtrribute】而且都实现了【Validate】方法
5.创建测试类 EmpInfo
public class EmpInfo
{
public int Id { get; set; }
[StringLengthAtrribute(Min = 8, Max = 10, ErrMsg = "工号格式有误")]
public string EmpNo { get; set; }
[IntRang(Min = 18, Max = 45, ErrMsg = "年龄不符合要求!")]
public int Age { get; set; }
}
6.测试
Console.WriteLine("*********************特性研究*****************");
var user = new EmpInfo()
{
Id = 0,
EmpNo = "A005485sg",
Age = 50
};
foreach (var item in MyPropValidate.Validate<EmpInfo>(user))
{
Console.WriteLine($"异常参数名称:{item.PropName};错误信息:{item.ErrMsg}");
}
var user2 = new EmpInfo()
{
Id = 0,
EmpNo = "A005485aaaaeb",
Age = 28
};
foreach (var item in MyPropValidate.Validate<EmpInfo>(user2))
{
Console.WriteLine($"异常参数名称:{item.PropName};错误信息:{item.ErrMsg}");
}
7.测试结果
五、总结
特性是对Type的额外描述,它也是一个类,其实在C#编译成中间语言IL的后,可以通过反编译软件看到有特性的地方其实会自动生成一个类的实例,但是在C# 中不能直接使用,所以在反射的时候可以获得该对象访问其中的属性和方法。本文的实例其实在系统中早也有实现,在using System.ComponentModel.DataAnnotations;中有很多属性约束,例如:[Required],[MaxLength()] 等等。这里只是想说明,属性一般怎么去用。