最近成功把「前端帶師」帶入C#的坑(實際是前端帶師開始從cocos轉unity遊戲開發了)
某天,「前端帶師」看到這段程式碼後問了個問題:[這個是裝飾器]?
[HttpGet]
public Response Get() {
return ...
}
我第一反應覺得不是,這玩意在C#中叫「特性」(英文名Attribute,下文統稱為特性),在Java中叫註解,雖然寫法和Python/TypeScript中的差不多,但印象中實現方式應該是不同的。
但咱學理工科的就是要嚴謹,不能僅憑經驗和感覺,為此,我查了一下資料,看了之前楊旭大佬推薦的《C# in nutshell》這本書,不僅確認了這個問題的答案,也對Attribute有了更多瞭解。
「特性」、裝飾器,其實都是設計模式中的裝飾器模式,同時也是AOP思想。
AOP是Aspect Oriented Programming,即面向切面程式設計。
AOP把系統分解為不同的關注點,或者稱之為切面(Aspect),是一種在執行時,動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想
比如現在有一個網站,有購物、社交、遊戲等多種功能且對所有使用者開放,現在需要限制只有高階會員才能使用其中的幾個功能,我們可以在每個模組加上if判斷,但這樣侵入性太強,且會造成大量重複程式碼;換成AOP的方法就是使用裝飾器,在需要高階會員的地方加上限制就行~
先來看看語法上的不同
先來看看Python中的裝飾器,在Python中函數是一等公民,裝飾器也是個函數,其內部又內嵌了另一個函數
def outer(func):
def inner():
# ... 一些程式碼
result = func()
return result
return inner
使用的時候
@outer
def test():
print('test')
使用時語法和Java的註解一樣,以@開頭
其實這是個語法糖,實際的效果等同於
outer(test)
將test
函數作為引數傳入給裝飾器,之後這段程式碼的執行順序如下:
def outer(func)
:裝飾器定義@outer
:裝飾器語法糖,直接直接執行outer
函數,將test
函數作為引數傳入outer
:執行裝飾器語法規則,將test
函數替換成inner
函數inner
:執行inner
函數程式碼test
:根據inner
中的這行程式碼:result = func()
,執行test
函數程式碼在Python這種動態語言中,實現裝飾器模式確實是比靜態語言容易的,被裝飾的內容作為引數傳入裝飾器,裝飾器可以直接存取到被裝飾的內容進行一些處理。
C#中,「特性」是一個類,繼承自Attribute
類,然後可以包含任意你想要的屬性欄位
用AttributeUsage
特性修飾,可以指定該特性可以修飾哪些程式碼元素
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DemoAttribute : Attribute {
public DemoAttribute(string param1) {
this.param1 = param1;
}
public string param1 { get; set; }
}
構造方法中的引數,就是使用特性時傳入的引數,比如這樣:
[DemoAttribute("class")]
public class DemoClass {
[Demo("method")]
public void Method1() {
}
}
PS:特性使用時可以省略後面的"Attribute",所以
DemoAttribute
和Demo
是同個東西
這樣寫了之後並不會產生什麼效果
因為特性只是單純的裝飾
在程式碼執行的時候,C#編譯器先範例化DemoAttribute
這個類,然後再範例化DemoClass
這個類,且在DemoAttribute
內是無法獲取到被裝飾的內容的。
為了使裝飾起效果,需要搭配使用反射~
反射指程式可以存取、檢測和修改它本身狀態或行為的一種能力。
通過下面的程式碼可以獲取到裝飾在DemoClass
上的特性
var info = typeof(DemoClass);
var attributes = info.GetCustomAttributes(true);
通過下面的程式碼可以獲取被裝飾的方法,和裝飾引數啥的
foreach (var methodInfo in typeof(DemoClass).GetMethods()) {
var attribute = (DemoAttribute) Attribute.GetCustomAttribute(methodInfo, typeof(DemoAttribute));
if (attribute != null)
Console.WriteLine("方法 {0} 被裝飾,裝飾引數 {1}", methodInfo.Name, attribute.param1);
}
獲取到這些資訊後,通過反射提供的其他功能再進行一些處理,也就實現了所謂的AOP
所以,C#的特性和Python/TypeScript中的裝飾器,雖然寫法用法不一樣,但殊途同歸,要實現的目的確實是差不多的。
但要說是同樣的東西又不嚴謹,所以應該同樣的東西,不過都是各自語言中實現AOP的方式。