C#的Delegate 為什麼沒在其他主流語言中普及?

對C#了解不多,簡單地看了下 Using Delegates (C# Programming Guide) 這個文檔。

感覺簡單的Delegate(singlecast)好像跟『函數指針』比較接近,目前『匿名函數』、『函數是一等公民』等理念的逐漸普及與Delegate的設計思路也是符合的。

而Multicast Delegate,或者簡單地說,就是兩個『函數』可以通過相加進行串聯,這個特性似乎在其他主流GUI編程語言(Java,C++,Javascript)中沒有出現,這能說明這個語言特性不太實用嗎?

此外,似乎在所有主流語言中,只有C#把『Delegate』這個概念明確地(explictly)單獨提了出來,並作為一種語言特性(語法糖)實現,這樣做是否有必要呢?

有人說『lambda』(或者說支持閉包的匿名函數)可以代替『delegate』的使用,如果真是這樣,為什麼不一開始就直接用lambda呢(畢竟那也不是什麼新鮮概念)?何必再『發明』delegate這個概念出來呢?


首先是delegate的設計其實是有一些歷史問題的,並不能說是最好的一種設計。

一個典型的問題就在於所有的delegate實例都是MulticastDelegate類型的,但事實上多播委託的使用範圍並沒有那麼大。更有意思的是多播委託本質上是個串列委託,委託方法是一個接一個的執行的。而實際應用場景中我們會遇到並發多播,非同步多播,當某個出現錯誤時繼續執行其他方法的多播委託,所有這些都是MulticastDelegate搞不定的。所以變得意義不大。

到今天為止MulticatsDelegate和+=的運算符重載還是多用於事件處理,而事件用默認的多播委託實現還會有可能導致對象不被釋放的坑。

其次就是delegate這個概念意義並不大,儘管在強類型語言裡面我們的確需要發明一種東西來描述函數簽名,並將單個函數簽名固化成一種強類型。但絕大多數時候專門去強調這個概念的意義並不大。很多語言都支持這個特性,但是一般可以直接用函數來描述就可以了,不必另外發明一個委託的概念。

另外就是傳統的delegate強類型還有一個缺陷,即使兩個delegate類型所代表的函數簽名是一模一樣的,那他們倆也是兩個類型。這在實際運用中是個麻煩。如果你需要用到兩個函數庫,而這兩個庫分別將某個類型的函數簽名定義了一個委託,即使你只需要寫一個函數就能滿足兩個函數庫的要求,但你仍然不得不莫名其妙的創建兩個委託實例分別給到兩個不同類型的委託對象。

這個缺陷直接催生了Func和Action系列的委託。

當Func和Action系列委託出現以及泛型委託類型參數的協變和逆變出現後,我們發現委託這個概念大部分時候變得很多餘。

也就是說我們可以輕鬆地寫出很多代碼根本用不著了解委託這個概念,我們最終的著眼點還是函數簽名。

但是別忘了泛型和匿名方法是C# 2.0才出現的(省略委託實例創建表達式直接用方法名稱代替委託實例也是2.0才引入的),而泛型委託類型參數的協變和逆變是C#3.0才出現的,C#一直在發展的過程中。還有大家所說的lambda表達式也是3.0才引入的。

無論怎樣,現在設計一個語言在語言內部保留委託的概念是很正常的,但是再花時間去把這個概念作為亮點來介紹,以及專門去學習和闡述是沒有什麼意義的。

即使是Java,其實那個SAM Type就是委託的別名,或者換句話說delegate就是SAM Type的別名和語法糖。

當然不管怎麼樣,C#的delegate語法相較於C/C++的函數指針的語法是一個巨大的飛越,而委託這種語法糖也遠比所謂的SAM Type直觀和簡便

當然我也看到很多人說委託的學習成本太高,我想說其實OOP和強類型編程語言的學習成本本來就略高於平均智商水平,早日發現並作出正確的選擇是非常對的。


這裡有個概念問題,Lambda表達式是語言特性,但Delegate只是這個特性在C#/.NET里的實現方式,別的語言有其他實現方式,比如Java里的SAM介面,理論上說C#要這麼做沒什麼技術難度,只是C#早就引入了Delegate這種「安全的方法指針」這種東西了。


delegate就是一個函數類型。你在靜態類型語言裡面,無論幹什麼東西都是需要一個類型的,譬如寫一個lambda表達式。不過C#這裡處理的比較奇怪,因為delegate是帶有名字的,所以lambda表達式無法變成delegate,編譯期也認為lambda表達式是沒有類型的——但是他可以隱式轉換成一個compatible的其他delegate類型。

題主只是庫寫的比較少,只是lambda的生產者,不是lambda的消費者,不然也不會問這種問題。你寫一個函數,其中一個參數接受一個lambda表達式,那你覺得這個參數的類型應該是什麼?

只能是delegate。


delegate就是把函數包裝成對象,然後傳來傳去。

為了滿足high order function(函數做參數和返回值)的需求,c#就搗鼓出了delegate。

在一些支持函數對象的語言(js,Python)中自然不需要把它包起來。

High Order Function 實例:

SICP python版原文:

def summation(n, term, next):
total, k = 0, 1
while k &<= n: total, k = total + term(k), next(k) return total

C#delegate版本 (Func&<&>相當於 singlecast delegate)

public double Sumation(int n, Func& term, Func& next)
{
double total = 0;
int k = 1;
while (k&<=n) { total += term(k); k = next(k); } return total; }


支持這種用法的語言海了去了。

能用 lamdba 的地方還有誰會用 delegate 啊。親,你就是其他語言玩的少。


因為需要類似「函數指針」的玩意,但C#默認沒有,所以就出來了個委託。

當然,我個人還希望能出個多繼承,但這個似乎很難啊……


其他有 closure 的語言裡面一般就直接叫 function 了,multicast 的也很容易模擬。

C# 這個名字和 J++ 有關,那時候 anonymous function、closure 還不流行。


Java里的SAM其實和C#里的delegate差不多,你可以就當它是delegate起了個新名字。

從C++的角度來看,一個delegate就是object.method(arg),Borland曾經認為C++需要這個東西,後來的bind/function/lambda表示Borland當年想多了。

Python/Ruby/JS之類的全動態語言紛紛表示,delegate是啥?能吃么?


思想都是相同的。其他語言沒有單獨提出來,那是因為每門語言都是自己的思維慣性(或許也是怪癖)和實際情況。這類現象很多的,比如Python就偏不搞interface關鍵字,PHP就偏要帶use玩閉包。

具體來說,為什麼C# 要搞delegate,而其他語言不搞?

1. C#的delegate是函數類型包裝類(本質上還是類),目的是用來搭載函數的。

2. Java其實也支持類似函數指針的東西,比如Method類,但是大家認為這玩意兒不安全,速度又慢,最關鍵的是丫的不符合OOP直覺——按照Java的思維
習慣,統統應該用介面代替這玩意兒。所以你看到,哪怕是JavaSE8裡面的匿名函數其實都是functional interface的實例對象。

3. C/C++可以搞單獨的函數指針,delegate的概念當然不用單獨提出來。

4. 至於動態腳本語言,比如JavaScript,Python,人家function本來就是一等公民,更沒必要單獨提出delegate了。

C#delegate多播把事件處理器串聯起來實用嗎?實用啊!觀察者模式嘛!只不過C#裡面的event關鍵字把事件、事件監聽器的概念融合到一起,捆綁到了類(事件源)上。加上C#可以重載運算符的特性,所以就有了+=語法糖。

Java為啥不搞?因為Java他爹認為運算符重載會加大複雜程度,搞得和C++一樣複雜臃腫。

至於lambda和delegate還是兩回事。輪子哥已經解釋的很清楚了。

總結:

C# delegate是函數包裝類,其他語言函數要麼是一等公民,要麼是跟Java那樣搞個介面來規範。C# delegate多播+=運算符,類比於觀察者模式,不過Java搞不了+=,因為沒有重載運算符的特性。C++是可以搞個類似的+=的,不過人家直接有指針,不用帶delegate玩……

歸根揭底,思想是相通的,只不過每門語言都有自己的實際情況,不能照搬西方那一套,所以我們還要學習一個。


函數式編程的概念很早就有了,可以說 C# 在有匿名委託後才真正意義上的滿足了函數式編程的大環境。 lambda 表達式作為語法糖,不同的語言有不同的實現方式,C# 本身就有 delegate,與 lambda 這個後來者苟且在一起就順其自然了。

delegate 是一種類型,地位和 string、int... 一樣,lambda 的工作是創建這個類型的一個實例,這部分工作由編譯器隱藏起來了,所以大部分時候 lambda 是可以當作匿名函數看待的:

public Form1()
{
var a = new Action(() =&> { }); //都是 Action 類型的實例
Action b = () =&> { };
Action c = delegate() { };
var d = (Action)(() =&> { });

(new Action(() =&> { }))(); //閉包

a += b += c += d += (new Action(() =&> { }));
}

C# 是強類型的,即便是匿名函數,實際上也是由編譯器來識別具體類型,所以下面的代碼是錯誤的,因為最基本的 delegate 需要簽名:

var f = (Delegate)(() =&> { }); //編譯錯誤

如果寫成這樣就是沒問題:

delegate void action();
public Form1()
{
var e = (action)(() =&> { }); //編譯通過
}

C#的多播委託雖然不完美,但有其存在的意義,尤其是跟 event 聯繫緊密,比如觀察者模式、動態監聽事件、動態載入插件、模塊化(module)、解耦。。。這些其他語言也能實現,而 C# 的實現更加簡潔,直觀(編譯器背後要做的工作量很大……)。

大部分其他語言也不是沒有 delegate,只不過各有各的表達方式,個人最喜歡的是 JS 這種動態語言的實現方式(不考慮性能和安全性):

(function() {})();


我就想說有了lambda 誰還用委託啊 括弧雖然背後用的還是委託但是我少打好多代碼啊括弧括弧逃


C 有函數指針。

然而對於面向對象的語言來說,「保存」一個方法除了需要記錄這個方法的地址和簽名,還需要記錄這個方法作用在哪個實例上,所以只有函數指針是不夠的,需要有一種新的類型來表示『函數指針+目標實例』。

C# 的是委託,Java 的是函數式介面,一些動態類型語言的是綁定的函數(bound function),比如 Python 的 obj.func 的類型(自動綁定)和 JS 的 obj.func.bind(obj) 的類型。

lambda 是一個實例,委託是一種類型,就像 1 和 int。


oc也可以算主流


objective c裡面全是delegate......


實際上你說的很對,delegate就是可以對應c/c++中的函數指針,當然,在c#中的delegate比普通的函數指針更強大。

而lambda顯然跟函數指針不是一回事,說白了lambda就是當你懶得為一些極其簡單,只用一次的函數取個名字,或者多寫幾行代碼的時候,可以偷懶的語法糖。它是不能代替函數指針的。比起多寫幾行代碼,起個沒什麼用處的名字更加煩人。

實際上,比起delegate,lambda才是可有可無的。這也是為什麼大多數語言都很早就實現了類似函數指針的特性,而lambda,則支持的比較晚(c++也終於支持lambda了)。


不就是委託么 你把它當作一個變數 特殊的變數 表示一個方法


學習成本太高,語法糖封裝得太深,對於初學者像魔法一樣,我就不說當年學這個必須在紙上搞半天他的語法糖,後來轉到java,全用介面,一下就上手了。


我學了c語言再學的c#,在我眼中,delegate本質上就是函數指針。delegate被我稱為代表,或函數代表。因為它裡面含的是函數的指針,代表了某個函數。翻譯成委託是個什麼鬼?delegate本意不就是代表嗎?既然是函數指針,或者說類似於函數指針,那麼在其他語言中應該是無處不在的。


這個不就相當於c++中的指向函數的指針列表么


推薦閱讀:

請問wpf在設計界面時,是使用blend可視化設計,還是直接編寫xaml代碼呢?
如何看待 .NET Native,真能達到 C++ 的性能、C# 的生產效率嗎?
.Net core現在可以做什麼?未來發展有前景嗎?
如何評價 JetBrains .NET IDE 的正式版 Rider 2017.1 ?
VS里有什麼Eclipse里沒有的強大功能?

TAG:Web開發 | 圖形用戶界面 | Java | 面向對象編程 | C# |