C#中的“is”运算符返回不一致的结果

时间:2015-03-13 10:16:19

标签: c# .net

我想在C#中使用“is”运算符来检查对象实例的运行时类型。但它似乎没有像我期望的那样起作用。

假设我们有三个组件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版本化为强名称程序集并使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指令检查的情况下发出代码。对于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代码。

2 个答案:

答案 0 :(得分: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 :(得分:1)

您的问题是C#编译器将res1的等式编译为true(如IL所示)。但是res2正在执行正确的分析,因为它在运行时执行(任何时候你转换为object它迫使C#回退到大多数事情的运行时操作。)

所以看起来编译器假设类型是相同的(可能没有验证组成DLL的版本)。

很容易想到的唯一解决方案是更改其中一个的别名,看看是否有资格说明你正在谈论的C3。