2015-03-13 66 views
16

我想在C#中使用「is」運算符來檢查對象實例的運行時類型。但它似乎並沒有像我期望的那樣工作。「是」運算符在C#中返回不一致的結果

假設我們有三個組件A1,A2和A3,都只包含一個類。

A1:

public class C1 
{ 
    public static void Main() 
    { 
     C2 c2 = new C2(); 

     bool res1 = (c2.c3) is C3; 
     bool res2 = ((object)c2.c3) is C3; 
    } 
} 

A2:

public class C2 
{ 
    public C3 c3 = new C3(); 
} 

A3:

public class C3 
{ 
} 

A1需要引用A2和A3。

A2需要引用A3。

運行後Main()res1和res2設置爲true,如預期。當我開始將A3版本強制命名爲assembly並使A1引用一個版本 和A2引用另一個版本的A3(A3的源代碼保持不變)時,會出現問題。順便說一句。只有當A2引用的A3版本低於或等於A1引用的A3版本 時,編譯器才允許這樣做。這個程序的結果現在是不同的(res1 = true,res2 = false)。

此行爲是否正確?他們不應該都是假的(或者也許是真的)?

根據C#5.0規範(章節7.10.10),res1和res2應該以相同的值結束。 「is」操作符應始終考慮實例的運行時類型。

在IL代碼中,我可以看到res1編譯器做出了這樣的決定,即來自不同A3程序集的兩個C3類相等,並且發出代碼,而不用isinst指令檢查僅針對null。對於res2,編譯器添加了isinst指令,它推遲了運行時間的決定。 它看起來像C#編譯器有關如何解決此問題比CLR運行時不同的規則。

.method public hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  36 (0x24) 
    .maxstack 2 
    .locals init ([0] class [A2]C2 c2, 
      [1] bool res1, 
      [2] bool res2) 
    IL_0000: nop 
    IL_0001: newobj  instance void [A2]C2::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: ldfld  class [A3]C3 [A2]C2::c3 
    IL_000d: ldnull 
    IL_000e: ceq 
    IL_0010: ldc.i4.0 
    IL_0011: ceq 
    IL_0013: stloc.1 
    IL_0014: ldloc.0 
    IL_0015: ldfld  class [A3]C3 [A2]C2::c3 
    IL_001a: isinst  [A3_3]C3 
    IL_001f: ldnull 
    IL_0020: cgt.un 
    IL_0022: stloc.2 
    IL_0023: ret 
} // end of method C1::Main 

難道只是爲了更快和優化的實現而不使用isinst(考慮到編譯器警告)進行權衡?

解決此問題的可能選項是綁定重定向(如警告中所建議的),但我無法使用該版本,因爲版本可能不總是向後兼容(儘管C3類始終是)。改變A2中的引用對我來說也不是一種選擇。

編輯:因爲它似乎最簡單的解決方法是總是強制轉換爲對象以獲得正確的結果。

無論如何,它仍然很有趣,知道它是否是C#編譯器中的錯誤(並且可能向MS報告)或者本身沒有錯誤(因爲編譯器識別問題並報告警告),儘管它仍然可以生成正確的IL代碼。

+3

類型* *由於*不同*組件而有所不同。程序集的強名稱包括版本號 - 您明確指定兩個二進制文件不再相同,但您認爲它們不相同,但後者與較早版本兼容,前提是沒有公共類型會破壞兼容性。 C#規則處理語言,而您有兩個不同的IL二進制文件。 – 2015-03-13 10:28:39

+1

是的,這就是我期望的不同組件。那麼爲什麼res1設置爲true呢? – Erik 2015-03-13 11:07:45

+0

什麼是編譯器警告? – 2015-03-13 21:33:24

回答

2

不幸的是,我沒有答案爲什麼第一個結果是正確的。但是,如果規範說is應該基於運行時類型,Panagiotis是正確的;類型是不同的,都應該返回false。 GetType()和typeof的行爲應該如is

var res3 = c2.c3.GetType() == typeof(C3);    // is false 
var res4 = ((object)c2.c3).GetType() == typeof(C3); // is false 

var localC3 = new C3(); 
var res5 = localC3 is C3;        // is true 
var res6 = ((object)localC3).GetType() == typeof(C3); // is true 

我的膝蓋反應會擺脫鑄造的對象,因爲這似乎是你想要的。

但是,如果is已修復,則可能會發生變化。你可以訴諸以下。由於您的代碼是針對已簽名的程序集編譯的,因此用戶將無法替換僞裝程序集。

var res7 = c3.GetType().FullName == typeof(C3).FullName 

希望有一些幫助。

1

你的問題是res1的方程編譯爲true由C#編譯器(如IL所示)。然而,res2正在執行正確的分析,因爲它在運行時正在進行分析(任何時候你投到object都會迫使C#回退到大多數情況下的運行時操作)。

因此,編譯器認爲類型相同(可能不驗證組成DLL的版本)。

容易想到的唯一解決方案是更改其中一個的別名,並查看是否符合您正在討論的C3的幫助。

相關問題