特性
在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.只是標記了一個特性,就可以獲取了一個驗證的邏輯。
這樣就完成了特性添加額外信息和特性添加額外功能的作用。