2010-03-15 50 views
12

考慮下面的代碼:C# - 關閉初始化程序中的類字段?

using System; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var square = new Square(4); 
      Console.WriteLine(square.Calculate()); 
     } 
    } 

    class MathOp 
    {   
     protected MathOp(Func<int> calc) { _calc = calc; } 
     public int Calculate() { return _calc(); } 
     private Func<int> _calc; 
    } 

    class Square : MathOp 
    { 
     public Square(int operand) 
      : base(() => _operand * _operand) // runtime exception 
     { 
      _operand = operand; 
     } 

     private int _operand; 
    } 
} 

(!忽視了一流的設計;我不是實際編寫一個計算器的代碼只是表示該了一段時間來縮小一個更大的問題最小攝製)

我會期待它可以:

  • 打印「16」,或
  • 拋出一個編譯時錯誤,如果關閉了成員字段,在這種情況下是不允許的

相反,我得到一個無意義的異常拋出在指定的行。在3.0 CLR上它是一個NullReferenceException;在Silverlight CLR上它是臭名昭着的操作可能會破壞運行時。

+0

它不爲我編譯....「非靜態字段,方法或屬性'ConsoleApplication2 .Square._operand'」需要對象引用。這是你的確切代碼嗎? –

+0

是的,這是一個複製/粘貼,它爲我編譯。 –

+0

請注意,我在VS2008上 - 正如Aaron指出的那樣,2010年編譯團隊可能已將此列爲錯誤(即與我同意:)) –

回答

11

它不會導致編譯時錯誤,因爲它是有效的閉包。

問題是在創建閉包時this尚未初始化。提供該參數時,您的構造函數尚未實際運行。所以產生的NullReferenceException其實很合乎邏輯。這是this那就是null

我會證明給你。讓我們用這種方式重寫代碼:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var test = new DerivedTest(); 
     object o = test.Func(); 
     Console.WriteLine(o == null); 
     Console.ReadLine(); 
    } 
} 

class BaseTest 
{ 
    public BaseTest(Func<object> func) 
    { 
     this.Func = func; 
    } 

    public Func<object> Func { get; private set; } 
} 

class DerivedTest : BaseTest 
{ 
    public DerivedTest() : base(() => this) 
    { 
    } 
} 

猜猜這是什麼打印?是的,它是true,關閉返回null,因爲this執行時未初始化。

編輯

我很好奇托馬斯的說法,想,也許他們會改變行爲在隨後VS釋放。我實際上發現了一個關於這件事情的Microsoft Connect issue。它因「不會修復」而關閉。奇。正如微軟在迴應中所說的那樣,在基礎構造函數調用的參數列表中使用this引用通常是無效的;在那個時間點參考根本不存在,如果您嘗試使用「裸體」,實際上會出現編譯時錯誤。所以,可以說應該產生一個編譯錯誤的關閉情況,但this參考是隱藏的編譯器,其中(至少在VS 2008)將不得不知道看看它關閉內,以便阻止人們這樣做。它不,這就是爲什麼你最終會發生這種行爲。

+0

你試過了嗎?我收到一個編譯錯誤... –

+0

@Thomas Levesque:是的,我編譯了,並且得到了相同的運行時錯誤。好奇你有編譯錯誤;我在VS 2008上,你在VS 2010上?也許他們把這個分類爲一個bug並且更新了編譯器來檢測這個? – Aaronaught

+0

+1好解釋。我懷疑它,但不能確定在我的Watch Window中缺少/ this /指針不僅僅是一個VS怪癖(我發現它太容易混淆了)。 –

0

你試過用() => operand * operand代替嗎?問題在於,當您打電話給基地時,無法確定_operand會被設置。是的,它試圖在你的方法上創建一個閉包,並且不保證這裏的東西的順序。

由於您沒有設置_operand,我建議您只使用() => operand * operand

+1

它會工作,但它有一個非常不同的含義... –

+0

只需說這失敗了目的。在我的「真實」代碼中,我有幾個非常複雜的MathOps。一些步驟對所有MathOps都是通用的,所以我將它們放在基類中。在一個特定的操作中,計算的第一部分是不變的 - 我想通過將中間結果緩存到成員字段中進行優化,然後讓剩餘的計算(根據參數變化計算)按照常規進行。 –

+1

@Richard Berg:也許你可以通過使用受保護的初始化方法來解決問題?我相信你已經想到了這一點,但它不能提及... – Aaronaught

2

如何:

using System; 
using System.Linq.Expressions; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var square = new Square(4); 
      Console.WriteLine(square.Calculate()); 
     } 
    } 

    class MathOp 
    { 
     protected MathOp(Expression<Func<int>> calc) { _calc = calc.Compile(); } 
     public int Calculate() { return _calc(); } 
     private Func<int> _calc; 
    } 

    class Square : MathOp 
    { 
     public Square(int operand) 
      : base(() => _operand * _operand) 
     { 
      _operand = operand; 
     } 

     private int _operand; 
    } 
} 
+0

推遲解決方案的有趣方式。儘管如此,2010年仍然不起作用。 – Jimmy

+0

聰明。 +1爲最快的修復(不需要重構)。 –

14

這是已經固定的編譯器錯誤。代碼首先不應該是合法的,如果我們要允許它,我們至少應該生成有效的代碼。我的錯。不便之處,敬請原諒。