特性的学习
特性
在C#的编程过程之中,我们可以发现特性是无处不在的,那么什么是特性那?
什么是特性?
- 特性其实就是一个类(class),声明的时候,默认以Attribute结尾,直接或者间接的继承Attribute抽象类。
- 当我们应用特性的时候,是把这个特性以[]包裹标记在类或者类内部成员上。
特性和注释的区别?
- 注释只是一个描述;在编译后,是不存在的。
- 特性
如何自定义特性,特性的多种标记
- 标记的是,如果是以Attribute结尾,Attribute可以省略掉,直接或间接继承Attribute。
- 可以标记类的内部的任何成员上。
- 特性在标记的时候,其实就是调用构造函数。
- 在标记的时候也可以对公开的属性或字段赋值
- 默认是不能重复标记的
- AttributeUsage:也是一个特性,是用来修饰特性的特性
- AttributeTargets指定当前特性只能标记到某个地方:建议大家在自己定义特性的时候,最好能够明确指定AttributeTargets--明确告诉别人,我这个特性是专门用来标记在哪里的。
- AllowMultiple:是否可以重复标记。
- Inherited:是否可以继承特性
定义一个特性:
[AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]
public class CustomAttribute : Attribute
{
private int _Id { get; set; }
public string _Name { get; set; }
public int _Age;
//public CustomAttribute()
//{
//}
public CustomAttribute(int id)
{
this._Id = id;
}
public CustomAttribute(string name)
{
this._Name = name;
}
public void Do()
{
Console.WriteLine("this is CustomAttribute");
}
}
如何调用到特性内部的成员--自定义的这个特性,如何才能使用它那?
- 进入反编译以后,在标记有特性的类的内部,生成了custom的成员,但是我们不能直接调用;--要通过反射调用。要让特性生效,实际上来说就是要去执行我们这个特性?--就需要构造特性的实例。
- 在使用反射获取特性的时候,可以把标记在任何地方的特性都可以获取到
- 既然标记的特性可以通过反射来获取到实例,就可以加以应用
code:
public class InvokeAttributeManager
{
/// <summary>
/// 通过反射来调用特性---确实是可以调用到特性的实例的
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void Show(Student student)
{
//Type type = typeof(Student);
Type type = student.GetType();
//1.先判断是否有特性
if (type.IsDefined(typeof(CustomAttribute), true))
{
//2.获取--先判断再获取--为了提高性能
foreach (CustomAttribute attribute in type.GetCustomAttributes(true))
{
Console.WriteLine($"attribute._Name:{attribute._Name}");
Console.WriteLine($"attribute._Age:{attribute._Age}");
attribute.Do();
}
}
//获取当前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 对应的就是这个枚举;数据库中保存的是数字,但是我们在查询到数据以后,展示给界面的时候,就可以通过特性来实现。
public enum UserStateEnum
{
Normal = 1,
Frozen = 2,
Deleted = 3
}
面对上述的情况,我们往往需要通过分支判断来判断状态,在面对描述信息改了?我们就需要每一个使用此枚举的地方,导致工作量的增加。
二、通过特性来获取特性信息---额外信息---特性获取额外信息
定义一个特性类
[AttributeUsage(AttributeTargets.Field)]
public class RemarkAttribute:Attribute
{
private string _Desc;
public RemarkAttribute(string desc)
{
_Desc = desc;
}
public string GetRemark()
{
return this._Desc;
}
}
}
调用特性方法
public class RemarkAttributeExtension
{
public static string GetRemark(UserStateEnum userState)
{
Type type = userState.GetType();
string fieldName = userState.ToString();
FieldInfo? fieldInfo = type.GetField(fieldName);
if (fieldInfo.IsDefined(typeof(RemarkAttribute), true))
{
RemarkAttribute attribute =fieldInfo.GetCustomAttribute<RemarkAttribute>();
return attribute.GetRemark();
}
return userState.ToString();
}
}
好处:
- 如果增加了新的字段,就可以直接获取不用其他改动
- 描述修改后,获取描述信息的方法不用修改
这样我们还可以更改我们的RemarkAttributeExtension类变成扩展方法
public static class RemarkAttributeExtension
{
public static string GetRemark(this Enum @enum)
{
Type type = @enum.GetType();
string fieldName = @enum.ToString();
FieldInfo? fieldInfo = type.GetField(fieldName);
if (fieldInfo.IsDefined(typeof(RemarkAttribute), true))
{
RemarkAttribute attribute =fieldInfo.GetCustomAttribute<RemarkAttribute>();
return attribute.GetRemark();
}
return @enum.ToString();
}
}
更改我们的代码为;
UserStateEnum Normal = UserStateEnum.Normal;
UserStateEnum Frozen = UserStateEnum.Frozen;
UserStateEnum Deleted = UserStateEnum.Deleted;
Console.WriteLine(RemarkAttributeExtension.GetRemark(Normal));
Console.WriteLine(RemarkAttributeExtension.GetRemark(Frozen));
Console.WriteLine(RemarkAttributeExtension.GetRemark(Deleted));
Console.WriteLine(Normal.GetRemark());
Console.WriteLine(Frozen.GetRemark());
Console.WriteLine(Deleted.GetRemark());
这样我们通过反射+特性+扩展方法,可以封装一个获取额外新的公共方法。
特性获取额外功能
添加一个功能
- 如果要保存一条数据到数据库中去
- 从前端提交过来的数据格式为:
{
"id":0,
"name":"string",
"age":0
"state":1
}
3.包含了很多字;
4.如果数据库总Name的值要求存储的长度为40个字符---如果保存的数据超过40个字符---肯定会报错 - 肯定要在保存之前就是需要验证这行数据。
我们先验证我们的用户名不能为空的一个验证:
[AttributeUsage(AttributeTargets.Property)]
public class Required:Attribute
{
public string _ErrorMessage;
public Required(string message) {
this._ErrorMessage = message;
}
public bool Validate(object value)
{
bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
return bResult;
}
}
之后,就是我们调用特性的代码
public class ValidateInvokeManager
{
public static bool Validate<T>(T t) where T : class
{
Type type = typeof(T);
foreach (var prop in type.GetProperties())
{
if (prop.IsDefined(typeof(Required),true))
{
Required required = prop.GetCustomAttribute<Required>();
if (required.Validate(prop.GetValue(t))==false)
{
return false;
}
}
}
return true;
}
}
在这里,只是添加一个用户名非空的一个验证,下面是我们的用户类
public int Id { get; set; }
[Required("不能为空")]
public string Name { get; set; }
public int Age { get; set; }
public long QQ { get; set; }
public string Mobile { get; set; }
public UserStateEnum UserState { get; set; }
这里我们再添加一个长度的限制
public class LengthAttribute : Attribute
{
public string _ErrorMessage;
private int _Min;
private int _Max;
public LengthAttribute(int min, int max)
{
this._Min = min;
this._Max = max;
}
public bool Validate(object value)
{
if (value != null)
{
if (value.ToString().Length < _Min || value.ToString().Length > _Max)
{
return false;
}
else
{
if (value.ToString().Length == 0)
{
return false;
}
else
{
return true;
}
}
}
return false;
}
}
对应的用户类:
public class UserInfo
{
public int Id { get; set; }
[Required("Name的值不能为空")]
public string Name { get; set; }
public int Age { get; set; }
public long QQ { get; set; }
[Required("Mobile的值不能为空")]
[Length(11, 11, _ErrorMessage = "手机号必须为11位数")]
public string Mobile { get; set; }
public UserStateEnum State { get; set; }
public string UserStateDescription
{
get
{
return this.State.GetRemark();
}
}
}
这样其实并不是很通用,我们再去更改一个通用的版本。
定义一个抽象的类
public abstract class AbstractAttribute : Attribute
{
public abstract bool Validate(object oValue);
}
}
改造一下我们对应的验证的类
[AttributeUsage(AttributeTargets.Property)]
public class Required: AbstractAttribute
{
public string _ErrorMessage;
public Required(string message) {
this._ErrorMessage = message;
}
public override bool Validate(object value)
{
bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
return bResult;
}
}
public class LengthAttribute : AbstractAttribute
{
public string _ErrorMessage;
private int _Min;
private int _Max;
public LengthAttribute(int min, int max)
{
this._Min = min;
this._Max = max;
}
public override bool Validate(object value)
{
if (value != null)
{
if (value.ToString().Length < _Min || value.ToString().Length > _Max)
{
return false;
}
else
{
if (value.ToString().Length == 0)
{
return false;
}
else
{
return true;
}
}
}
return false;
}
}
对应的验证类
public static bool Validate<T>(T t) where T : class
{
Type type = typeof(T);
foreach (var prop in type.GetProperties())
{
if (prop.IsDefined(typeof(AbstractAttribute), true))
{
object oValue = prop.GetValue(t);
foreach (AbstractAttribute attribute in prop.GetCustomAttributes())
{
bool apiResult = attribute.Validate(oValue);
if (apiResult == false)
{
return apiResult;
}
}
}
}
return true;
}
这样我们又遇到了一个问题,什么问题?我们仅仅是知道了我们验证失败了,但是因为什么才导致我们的验证失败的信息还不清楚。
我们开始引入ApiResult这个类,来解决这个问题.
public class ApiResult
{
public bool ret { get; set; }
public string _ErrorMessage { get; set; }
}
对应的验证类:
public class LengthAttribute : AbstractAttribute
{
public string _ErrorMessage;
private int _Min;
private int _Max;
public LengthAttribute(int min, int max)
{
this._Min = min;
this._Max = max;
}
public override ApiResult Validate(object value)
{
if (value != null)
{
if (value.ToString().Length < _Min || value.ToString().Length > _Max)
{
return new ApiResult() { ret = false, _ErrorMessage = this._ErrorMessage };
}
else
{
if (value.ToString().Length == 0)
{
return new ApiResult() { ret = false, _ErrorMessage = this._ErrorMessage };
}
else
{
return new ApiResult() { ret = true, _ErrorMessage =null};
}
}
}
return new ApiResult() { ret = true, _ErrorMessage = null };
}
}
public class RequiredAttribute : AbstractAttribute
{
public string _ErrorMessage;
public RequiredAttribute(string message) {
this._ErrorMessage = message;
}
public override ApiResult Validate(object value)
{
bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
if (bResult)
{
return new ApiResult()
{
ret = bResult
};
}
else
{
return new ApiResult()
{
ret = bResult,
_ErrorMessage = _ErrorMessage
};
}
}
}
调用特性的类
public static ApiResult Validate<T>(T t) where T : class
{
Type type = typeof(T);
foreach (var prop in type.GetProperties())
{
if (prop.IsDefined(typeof(AbstractAttribute), true))
{
object oValue = prop.GetValue(t);
foreach (AbstractAttribute attribute in prop.GetCustomAttributes())
{
ApiResult apiResult = attribute.Validate(oValue);
if (apiResult.ret == false)
{
return apiResult;
}
}
}
}
return new ApiResult { ret = true, _ErrorMessage = null };
}
运行的结果:
好处:
1.只要是把验证的规则特性定义好,就可以重新使用
2.如果需要验证哪个属性,就把特性标记在哪个属性上就可以了;
3.只是标记了一个特性,就可以获取了一个验证的逻辑。
这样就完成了特性添加额外信息和特性添加额外功能的作用。