Оператор "есть" в С# возвращает несогласованные результаты
Я хотел бы использовать оператор "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.