1. 基本了解
1.1 簡述說明
特性(Attribute
)本質上是一個類,此類需要直接或間接繼承 Attribute
類,特性為目標元素(比如類、方法、結構、枚舉、組件等)提供關聯附加信息,並在運行期以反射的方式來獲取附加信息
說明:特性類的實例里沒有驗證邏輯,只有驗證用到的規范數據(比如字符串長度)、提示信息等,而驗證邏輯需要自己寫
.Net
框架提供了兩種類型的特性:預定義特性和自定義特性
1.2 特性應用
添加額外信息
可以使用特性附加需要的信息,例如:字段的中文名,實體字段對應數據表字段的名稱
示例:Table
特性,指定表名,Route
指定路由路徑
其它功能
例如,信息的效驗,功能標識
示例:值的長度效驗,類型效驗等(Model
驗證就是很好的例子)
2. 特性限定
2.1 AttributeUsage 限定
AttributeUsage
是 Attribute
的 Attribute
,用戶指定特性使用限制,常用的有:AttributeTargets
,AllowMultiple
2.2 AttributeTargets 目標限定
使用 AttributeTargets
表示指定Attribute
限制用於哪類實體上,在這里,實體是指: class
、method
、constructor
、field
、property
、GenericParameter
或者用All
,表明可用於所有實體
每個target
標記可以用|
鏈接(組合),如AttributeTargets.Class|AttributeTargets.Method
表示可用於class
或者method
上,以此為例
示例:無限定
[AttributeUsage(AttributeTargets.All)]
public class AllTargetsAttribute : Attribute {}
示例:限定只能標記在類上
[AttributeUsage(AttributeTargets.Class)]
public class ClassTargetAttribute : Attribute {}
示例:限定只能標記在方法上
[AttributeUsage(AttributeTargets.Method)]
public class MethodTargetAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true)]
public class MethodTargetAttribute : Attribute {}
示例:限定只能標記在構造函數上
[AttributeUsage(AttributeTargets.Constructor)]
public class ConstructorTargetAttribute : Attribute {}
示例:限定只能標記在字段上
[AttributeUsage(AttributeTargets.Field)]
public class FieldTargetAttribute : Attribute {}
示例:限定只能標記在泛型類型參數上
[AttributeUsage(AttributeTargets.GenericParameter)]
public class GenericParameterTargetAttribute : Attribute {}
示例:限定標記在類與方法上
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MethodAndClassTargetAttribute : Attribute {}
2.2 AllowMultiple 重復限定
使用 AllowMultiple
表示是否可以多次標記在同一目標上,不指定默認 true
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true)]
public class CustomAttribute : Attribute {}
3. 自定義特性
3.1 自定義步驟
- 聲明自定義特性,創建類
- 構建自定義特性,寫邏輯,功能
- 在目標程序元素上應用自定義特性
- 通過反射訪問特性,調用特性邏輯,功能
3.2 定義特性
基本定義
一個新的自定義特性應派生(繼承)自 System.Attribute
類
public class CustomAttribute : Attribute
{
...
}
帶構造函數定義,類中默認有個無參構造方法
public class CustomAttribute : Attribute
{
public CustomAttribute()
{
Console.WriteLine("調用子類無參構造函數");
}
public CustomAttribute(string text)
{
Console.WriteLine("調用子類有參構造函數:"+text);
}
}
帶屬性特性定義
public class CustomAttribute : Attribute
{
public int index { get; set; }
}
帶字段特性定義
public class CustomAttribute : Attribute
{
public string name;
}
3.3 標記使用
標記在類上
[CustomAttribute]
public class Studen
{
...
}
標記在方法上
public class Studen
{
[CustomAttribute]
public void Show() { }
}
標記在屬性上
public class Studen
{
[CustomAttribute]
public int id { get; set; }
}
標記在字段上
public class Studen
{
[CustomAttribute]
public int no;
}
標記在構造函數上
public class Studen
{
[CustomAttribute]
public Studen()
{
}
}
標記在方法返回參數上
public class Studen
{
[return:CustomAttribute] // 多個特性逗號隔開
public void Show()
{
}
}
4. 綜合示例
4.1 定義特性
定義驗證特性,使用抽象(類)特性實現擴展,實現邏輯在Validate
方法中
public abstract class AbstractValidateAttribute : Attribute
{
public abstract bool Validate(object oValue);
}
// 驗證值長度
public class LengthAttribute : AbstractValidateAttribute
{
public long Max { get; set; }
public long Min { get; set; }
public override bool Validate(object oValue)
{
return oValue != null && long.TryParse(oValue.ToString().Length.ToString(), out long lValue)
&& lValue >= Min && lValue <= Max;
}
}
// 驗證非空
public class NullAttribute : AbstractValidateAttribute
{
public override bool Validate(object oValue)
{
return oValue != null;
}
}
4.2 使用特性
public class Studen
{
[NullAttribute]
[LengthAttribute(Max =10,Min =5)]
public string name { get; set; }
}
4.3 調用特性
缺陷:需要手動調用擴展方法,且擴展方法沒有限制,功能單一(只能用於驗證)
public static class AttributeExtend
{
public static bool Validate<T>(this T t) where T : class
{
Type type = t.GetType();
foreach (var prop in type.GetProperties())
{
// 這里先判斷,是為了提高性能
if (prop.IsDefined(typeof(AbstractValidateAttribute), true))
{
object ovale = prop.GetValue(t);
// 獲取特性的實例,上面先判斷之后,再獲取實例
foreach (AbstractValidateAttribute attribute
in prop.GetCustomAttributes(typeof(AbstractValidateAttribute), true))
{
if (!attribute.Validate(ovale))
{
return false;
}
}
}
}
return true;
}
}
4.4 測試使用
static void Main(string[] args)
{
Studen stu = new Studen
{
name = "起舞在人間"
};
Console.WriteLine(stu.Validate());
}
5. 擴展補充
特性編譯后內容
通過反編譯工具得知,標記特性的元素,最終會在元素內部生成.custom
的元素
.class public auto ansi beforefieldinit ConsoleApp2.Studen
extends [mscorlib]System.Object
{
// Methods
.method public hidebysig
instance void Show () cil managed
{
.custom instance void ConsoleApp2.CustomAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2085
// Code size 2 (0x2)
.maxstack 8
IL_0000: nop
IL_0001: ret
} // end of method Studen::Show
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2088
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Studen::.ctor
} // end of class ConsoleApp2.Studen
特性,內部屬性
在特性中聲明屬性,且此屬性只能用於外部訪問,內部賦值
public int text { get; private set; }
特性應使用構造函數賦值還是使用屬性
當屬性為必填時使用構造函數,選填時使用屬性賦值
// 驗證最大長度時,最大長度必填,
[AttributeUsage(AttributeTargets.Property)]
public class MaxLenAttribute : Attribute
{
public int length { get; private set; }
public string remark { get; set; }
public MaxLenAttribute(int len)
{
this.length = len;
}
public bool Validate(object oValue)
{
return oValue.ToString().Length > this.length;
}
}
public class User
{
[MaxLenAttribute(5, remark = "超出長度!")]
public string name { get; set; }
}