2012-05-16 30 views
33

我一直認爲this在實例方法體內是不可能的。以下簡單的程序表明它是可能的。這是一些記錄的行爲?這個== null裏面的.NET實例方法 - 爲什麼可能?

class Foo 
{ 
    public void Bar() 
    { 
     Debug.Assert(this == null); 
    } 
} 

public static void Test() 
{    
    var action = (Action)Delegate.CreateDelegate(typeof (Action), null, typeof(Foo).GetMethod("Bar")); 
    action(); 
} 

UPDATE

我同意回答說這是怎麼這種方法記錄在案。但是,我不太瞭解這種行爲。特別是因爲這不是C#的設計方式。

我們已經得到了從別人的報告(使用C#的.NET組 的可能一個(還以爲是尚未命名C#當時))誰了 編寫代碼,上一個空叫的方法指針,但他們沒有 得到一個異常,因爲該方法沒有訪問任何字段(即 「this」爲空,但在方法中沒有使用它)。該方法然後調用另一種方法,它使用了這一點,並拋出了一個異常,並且隨後出現了一些頭部劃傷。在他們認爲它是 之後,他們給我們發了一張關於它的記錄。 我們認爲能夠調用一個空實例的方法是有點怪異的 。彼得戈爾德做了一些測試,看看哪些性能衝擊 總是使用callvirt,並且它足夠小,因此我們決定讓 進行更改。

http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx

+0

請參閱我的回答,指出CLR是有意設計的,就像.NET 1.0一樣。你引用的文章是關於一個不同的(非委託)情況,這將是一個編譯器優化 - 當靜態確定實例的類型時,可以通過直接調用替換「callvirt」。請注意,CLR必須在'callvirt'期間拋出'NullReferenceException'的原因是需要基於'this'引用的VMT查找;不僅僅是檢查參考的語義願望。 –

+0

相關:http://stackoverflow.com/q/3143498/158779 –

+0

恕我直言,很遺憾沒有標準方法(可能通過屬性)指定應該用'call'而不是'callvirt'調用方法,因爲這將允許封裝值的類型[例如'string']具有可以在默認值存儲位置上操作的成員。說'如果(someString.IsNullOrEmpty)'將比'if(String.IsNullOrEmpty(someString))'更清潔恕我直言。 – supercat

回答

23

因爲,您將nullDelegate.CreateDelegate

firstArgument所以你調用一個空的對象上的實例方法。

http://msdn.microsoft.com/en-us/library/74x8f551.aspx

如果firstArgument爲空引用和方法是一個實例方法, 結果取決於委託類型類型的簽名和 方法:

如果類型的簽名明確包含隱藏的第一個參數 方法,該代表據說代表一個開放的 實例方法。調用委託時,參數列表 中的第一個參數將傳遞給隱藏實例參數 方法。

如果方法和類型的簽名匹配(也就是說,所有參數 類型都是兼容的),那麼該委託被稱爲在空引用上關閉。調用委託就像在一個空實例上調用一個實例 方法,這對 不是特別有用。

12

當然,你可以,如果你正在使用呼叫IL指令或委託方法調用到的方法。如果您嘗試訪問會導致您所尋找的NullReferenceException的成員字段,您將只設置此陷阱陷阱。

嘗試

int x; 
public void Bar() 
{ 
     x = 1; // NullRefException 
     Debug.Assert(this == null); 
} 

的BCL甚至不包含明確這個== null檢查,以幫助調試不使用callvirt(如C#)所有的時間語言。有關更多信息,請參閱此question

例如,String類有這樣的檢查。對他們來說沒有任何神祕之處,除了你不會在C#等語言中看到他們的需要。

// Determines whether two strings match. 
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 
public override bool Equals(Object obj) 
{ 
    //this is necessary to guard against reverse-pinvokes and 
    //other callers who do not use the callvirt instruction 
    if (this == null) 
     throw new NullReferenceException(); 

    String str = obj as String; 
    if (str == null) 
     return false; 

    if (Object.ReferenceEquals(this, obj)) 
     return true; 

    return EqualsHelper(this, str); 
} 
5

嘗試關於Delegate.CreateDelegate()at msdn的文檔。

您正在「手動」調用所有內容,因此不會爲this指針傳入實例,而是傳遞null。所以可能會發生,但你必須非常努力地嘗試。

+0

請參閱以下示例,瞭解真正簡單的方法。 http://bradwilson.typepad.com/blog/2008/01/c-30-extension.html –

+0

擴展方法可能「看起來」像類方法,但它們確實不是。它們只是一些靜態方法,它們在一個類實例上運行,並帶有一些內置的易用性。所以我不會這麼稱呼。 –

+0

我同意你的觀點,但是列表不會停留在擴展方法中。 C++/CLI直接調用非虛擬C#方法,爲您提供與擴展方法示例完全一樣的源代碼,這次在C#端導致了一個真正的'this == null'。 –

5

this是一個參考,所以從類型系統的角度看它沒有問題,因爲它是null

你可能會問爲什麼NullReferenceException沒有被拋出。 CLR引發該異常的情況的完整列表是documented。你的情況沒有列出。是的,它 a callvirt,但到Delegate.Invokesee here)而不是Bar,所以this引用實際上是您的非空委託!

您看到的行爲對CLR有一個有趣的實施結果。代表有一個Target屬性(對應於您的this參考),這是相當頻繁的null,即當代表是靜態的(想象Bar是靜態的)。自然,現在有一個私人的財產支持領域,稱爲_target_target是否包含靜態委託的空值? No it doesn't.它包含對委託本身的引用。爲什麼不是null?因爲null是代理的合法目標,因爲您的示例顯示CLR沒有兩種風格的指針來以某種方式區分靜態代理。

這一點trivium表明,與委託,實例方法的空目標是沒有事後纔想到的。你可能仍然在問最終的問題:但爲什麼他們必須得到支持?

早期的CLR有一個雄心勃勃的計劃,即使對於發誓的C++開發人員來說也是成爲首選的平臺,這個目標首先是通過託管C++和C++/CLI來實現的。有些過於具有挑戰性的語言特徵被忽略,但是對於支持沒有實例的情況下執行的實例方法沒有任何真正的挑戰,這在C++中是完全正常的。包括代表支持。

因此,最終的答案是:因爲C#和CLR是兩個不同的世界。

More good readingeven better reading顯示允許空實例的設計,即使在非常自然的C#語法上下文中也顯示其痕跡。

0

這是C#類中的只讀引用。因此,如預期的那樣,這可以像任何其他參考(只讀模式)一樣使用... ...

this == null // readonly - possible 
this = new this() // write - not possible 
相關問題