&引用;是";C#中的运算符返回不一致的结果

&引用;是";C#中的运算符返回不一致的结果,c#,.net,C#,.net,我想在C#中使用“is”操作符来检查对象实例的运行时类型。但它似乎不像我预期的那样有效 假设我们有三个程序集A1、A2和A3,它们都只包含一个类 A1: A2: A3: A1需要参考A2和A3 A2需要参考A3 运行Main()后,res1和res2按预期设置为true。当我开始将A3版本化为强命名程序集并使A1引用一个版本时,就会出现问题 A2引用A3的另一个版本(A3的源代码保持不变)。顺便说一句,编译器仅在A2引用的A3版本低于或等于 A1引用的A3版本。这个程序的结果现在不同了(res1

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

假设我们有三个程序集A1、A2和A3,它们都只包含一个类

A1:

A2:

A3:

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指令的情况下仅对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#编译器中的一个bug(并可能报告给MS)还是一个bug本身(因为编译器识别了一个问题并报告了一个警告)仍然是一件有趣的事情,尽管它仍然可以生成正确的IL代码。

不幸的是,我不知道为什么第一个结果是真的。但是,如果规范说
应该基于运行时类型,那么Panagiotis是正确的;类型不同,两者都应返回false。GetType()和typeof的行为应与
is
should相同

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
我的下意识反应是摆脱这个物体,就像你想要的那样

但是,如果
是固定的,则这可能会改变。你可以采取以下措施。由于您的代码是针对签名程序集编译的,因此人们将无法替换假程序集

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

希望这能有所帮助。

您的问题是
res1
的等式被C#编译器编译为
true
(如IL所示)。然而,
res2
正在执行正确的分析,因为它是在运行时执行的(任何时候您对
object
强制C#在大多数情况下返回到运行时操作)

因此,编译器似乎假定类型相同(可能不验证组成DLL的版本)


唯一容易想到的解决方案是更改其中一个的别名,并查看限定您所谈论的C3是否有帮助。

类型不同,因为它们来自不同的程序集。程序集的强名称包括版本号——您明确指定两个二进制文件不再相同,但不考虑它们相同,但后面的一个与先前的二进制文件兼容,如果没有公共类型中断兼容性。当你有两个不同的IL二进制文件时,C#规则处理语言。是的,这就是我所期望的不同程序集。那么为什么res1设置为true呢?编译器警告是什么?A1应该引用A3的两个版本。如果您提供绑定重定向,代码将是可验证的,就像编译器警告CS1701所假设的那样。若您所做的只是一个
is
检查,那个么代码将是可验证的,但若您对来自不同程序集的
C3
实例调用任何方法,它将不会被验证。
.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
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
var res7 = c3.GetType().FullName == typeof(C3).FullName