Оператор "есть" в С# возвращает несогласованные результаты

Я хотел бы использовать оператор "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 остается тем же). Btw. компилятор допускает это только в том случае, если версия A3, на которую ссылается A2, меньше или равна версия A3, на которую ссылается A1. Результат этой программы теперь отличается (res1 = true, res2 = false).

Правильно ли это поведение? Не должны ли они быть ложными (или, возможно, истинными)?

В соответствии со спецификацией С# 5.0 (глава 7.10.10) оба res1 и res2 должны иметь одинаковое значение. Оператор "is" всегда должен учитывать тип экземпляра экземпляра.

В IL-коде я вижу, что для res1 компилятор сделал деление того, что оба класса C3, идущие из разных сборок A3, равны и испускал код без проверки инструкции только против null. Для res2-компилятора добавлена ​​инструкция isinst, которая откладывает решение для времени выполнения. Похоже, что компилятор С# имеет другое правило о том, как разрешить это, чем время выполнения 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 также не является для меня вариантом.

РЕДАКТИРОВАТЬ: Как кажется, самым простым обходным путем является всегда отбрасывать объект, чтобы получить правильный результат.

В любом случае было бы интересно узнать, является ли это ошибкой в ​​компиляторе С# (и, возможно, сообщать об этом в MS) или не является ошибкой как таковой (поскольку компилятор идентифицирует проблему и сообщает о предупреждении), хотя она все равно может генерировать правильную IL-код.

Ответы

Ответ 1

К сожалению, у меня нет ответа на вопрос, почему первый результат дает true. Однако, если спецификация говорит, что 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

Надеюсь, некоторые из них помогут.

Ответ 2

Ваша проблема в том, что уравнение res1 скомпилировано в true компилятором С# (как показано IL). Однако res2 выполняет правильный анализ, поскольку он делает это во время выполнения (в любое время, когда вы выполняете команду object, это заставляет С# возвращаться к операциям во время выполнения для большинства вещей).

Итак, похоже, что компилятор предполагает, что типы одинаковы (вероятно, не проверяя версию составной библиотеки DLL).

Единственное решение, которое приходит на ум легко - это изменить псевдоним одного из них и посмотреть, помогает ли квалификация, о которой вы говорите C3.