2009-09-02 94 views
125

爲什麼不能在lambda表達式中使用ref或out參數?不能在lambda表達式中使用ref或out參數

我今天遇到錯誤,發現一個解決方法,但我仍然好奇爲什麼這是一個編譯時錯誤。

這裏有一個簡單的例子:

private void Foo() 
{ 
    int value; 
    Bar(out value); 
} 

private void Bar(out int value) 
{ 
    value = 3; 
    int[] array = { 1, 2, 3, 4, 5 }; 
    int newValue = array.Where(a => a == value).First(); 
} 
+10

請問那是什麼你有解決辦法發現? – Beatles1692 2012-02-22 13:07:33

+0

這是關於迭代器的,但是這篇文章中的許多相同的推理(畢竟他也是語言設計團隊的Eric Lippert —)適用於lambdas: 2009-09-02 03:35:17

回答

94

Lambda表達式有改變,他們捕捉變量壽命的外觀。例如以下lambda表達式使參數p1到不是作爲後的方法幀是不再在堆棧上

Func<int> Example(int p1) { 
    return() => p1; 
} 

捕獲變量的另一個特性是它的值可被訪問的當前方法幀更長對lambda表達式以外的變量的更改也是可見的。例如,下面的打印42

void Example2(int p1) { 
    Action del =() => { p1 = 42; } 
    del(); 
    Console.WriteLine(p1); 
} 

這兩個屬性產生一組特定的,其在飛行ref參數的面通過以下方式

  • ref參數可以具有固定的壽命的效果。考慮將一個局部變量作爲ref參數傳遞給一個函數。
  • lambda中的副作用需要在ref參數本身上可見。在方法和調用者中。

這些是有些不兼容特性,是他們在lambda表達式不允許的原因之一。

+6

據我所知,我們不能在lambda表達式中使用'ref',但是使用它的願望並沒有被饋送。 – zionpi 2015-04-03 08:58:29

66

引擎蓋下,匿名方法是通過提升捕獲變量(這是你的問題的身體是關於什麼的),並存儲爲編譯器的領域實現生成的類。沒有辦法將refout參數存儲爲字段。 Eric Lippert在a blog entry中對此進行了討論。請注意,捕獲的變量和lambda參數之間存在差異。您可以「形參」之類的下面,因爲它們沒有捕獲變量:

delegate void TestDelegate (out int x); 
static void Main(string[] args) 
{ 
    TestDelegate testDel = (out int x) => { x = 10; }; 
    int p; 
    testDel(out p); 
    Console.WriteLine(p); 
} 
5

由於這是Google上「C#lambda ref」的最佳結果之一,我覺得我需要擴展上述答案。舊的(C#2.0)匿名委託語法可以工作,並且它支持更復雜的簽名(以及閉包)。 Lambda和匿名代表至少在編譯器後端共享感知實現(如果它們不相同) - 最重要的是,它們支持閉包。

我試圖做的時候我做了搜索,展示的語法:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition) 
{ 
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work. 
    return delegate(string text, ref int position, ref PositionInformation currentPosition) 
     { 
      var token = oldScanOperation(text, ref position, ref currentPosition); 
      if (token == null) 
       return null; 
      if (tokenDefinition.LeftDenotation != null) 
       token._led = tokenDefinition.LeftDenotation(token); 
      if (tokenDefinition.NullDenotation != null) 
       token._nud = tokenDefinition.NullDenotation(token); 
      token.Identifier = tokenDefinition.Identifier; 
      token.LeftBindingPower = tokenDefinition.LeftBindingPower; 
      token.OnInitialize(); 
      return token; 
     }; 
} 

只要記住,lambda表達式是程序上和數學上更安全(因爲前面提到的參考價值提升的):你可能會打開一罐蠕蟲。使用這種語法時要認真思考。

+3

我想你誤解了這個問題。問題是爲什麼lambda無法在其容器方法中訪問ref/out變量,而不是爲什麼_lambda本身_不能包含ref/out變量。 AFAIK對後者沒有好的理由。今天我寫了一個lambda'(a,b,c,ref d)=> {...}',並且'ref'帶有紅色下劃線,錯誤消息「Parameter'4'必須用'ref'關鍵字聲明」。捂臉!附:什麼是「ref value promotion」? – Qwertie 2014-05-14 19:53:31

+1

@Qwertie我得到這個完全參數化的工作,意思是,包括在a,b,c和d上的類型,它的工作原理。見BenAdams的回答(儘管他也誤解了原來的問題)。 – 2016-02-10 19:02:55

+0

@Qwertie我認爲我只刪除了這一點的一半 - 我認爲最初的觀點是將參數置於閉包中可能是有風險的,但我必須後來意識到這並不是在我給出的例子中發生的(也沒有我知道這是否會編譯)。 – 2016-08-23 13:48:17

39

你可以,但你必須明確定義所有的類型,因此

(a, b, c, ref d) => {...} 

是無效的,但是

(int a, int b, int c, ref int d) => {...} 

是有效

+3

雖然有趣,但這並沒有回答這個問題。嘗試再讀一遍 – edc65 2016-10-20 09:41:34

+8

它確實;問題是爲什麼你不能;答案是可以的。 – 2016-10-22 00:26:23

+11

它不;問題是爲什麼你不能在一個lambda內引用已經定義了'ref'或'out'的*現有變量*。如果您閱讀示例代碼,則很明顯(再次嘗試再次閱讀它)。接受的答案清楚地解釋了原因。你的答案是關於使用'ref'或'out' *參數*給lambda。完全不回答這個問題,並談論其他事情 – edc65 2016-10-22 09:51:40

相關問題