2015-03-13 148 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的帮助。

相关问题