Подтвердить что ты не робот

В С#/.NET почему sbyte [] совпадает с байтом [], за исключением того, что это не так?

Я просто заметил странное явление в С#/. NET.

Я создал этот минимальный пример, чтобы продемонстрировать:

if (new sbyte[5] is byte[])
{
 throw new ApplicationException("Impossible!");
}

object o = new sbyte[5];

if (o is byte[])
{
 throw new ApplicationException("Why???");
}

Это бросит "Зачем???", но не "Невозможно!". Он работает для всех массивов интегральных типов одинакового размера. Может кто-то объяснить это мне? Я смущен. Кстати, я использую .NET 4.

P.S.: Я знаю, что я могу получить ожидаемый результат, используя o.GetType() == typeof(byte[]).

4b9b3361

Ответ 1

Правила кастования CLR указывают, что это возможно. Правила С# говорят, что это невозможно. Команда С# сознательно решила, что они будут терпеть это отклонение от спецификации по различным причинам.

Почему CLR разрешает это? Наверное, потому что они могут удобно реализовать его. byte и sbyte имеют одинаковое двоичное представление, поэтому вы можете "обрабатывать" byte[] как sbyte[] без нарушения безопасности памяти.

Тот же трюк работает для других примитивных типов с одинаковым расположением памяти.

Ответ 2

Забавно, меня укусило в моем вопросе, Почему этот Linq Cast Fail при использовании ToList?

Jon Skeet (конечно) объясняет, что моя проблема - компилятор С#, по какой-то причине, думает, что они никогда не могут быть одним и тем же, и помогает ложный. Однако CLR делает это возможным. Приведение к объекту отменяет оптимизацию компилятора, поэтому оно проходит через CLR.

Соответствующая часть из его ответа:

Даже если в С# вы не можете напрямую передать байт [] в sbyte [], CLR позволяет:

var foo = new byte[] {246, 127};
// This produces a warning at compile-time, and the C# compiler "optimizes"
// to the constant "false"
Console.WriteLine(foo is sbyte[]);

object x = foo;
// Using object fools the C# compiler into really consulting the CLR... which
// allows the conversion, so this prints True
Console.WriteLine(x is sbyte[]);

Джоэл задал интересный вопрос в комментариях: "Это поведение контролируется флажком" Оптимизировать код "(/o для компилятора)?"

С учетом этого кода:

static void Main(string[] args)
{
    sbyte[] baz = new sbyte[0];
    Console.WriteLine(baz is byte[]);
}

И скомпилированный с помощью csc /o- Code.cs (не оптимизируйте), похоже, что компилятор все равно оптимизирует его. Полученный IL:

IL_0000:  nop
IL_0001:  ldc.i4.0
IL_0002:  newarr     [mscorlib]System.SByte
IL_0007:  stloc.0
IL_0008:  ldc.i4.0
IL_0009:  call       void [mscorlib]System.Console::WriteLine(bool)
IL_000e:  nop
IL_000f:  ret

IL_0008 загружает 0 (ложь) непосредственно в стек, затем вызывает WriteLine на IL_0009. Так что нет, флаг оптимизации не имеет значения. Если с CLR следует проконсультироваться, команда isinst будет использоваться. Вероятно, это выглядит примерно так, начиная с IL_0008:

IL_0008:  ldloc.0
IL_0009:  isinst     uint8[]
IL_000e:  ldnull
IL_000f:  cgt.un
IL_0011:  call       void [mscorlib]System.Console::WriteLine(bool)

Я бы согласился с поведением оптимизатора. Флаг оптимизации не должен изменять поведение вашей программы.

Ответ 3

VB.NET фактически "бросает" во время компиляции:

Выражение типа "1-мерный массив SByte" никогда не может быть типа "1-мерного массива байтов".

в эквиваленте первого оператора if.

И эквивалент второго if преуспевает (т.е. выдает кодированное исключение) во время выполнения, как ожидалось, потому что это тот же CLR.

Ответ 4

Здесь приведен более простой пример, который показывает ту же проблему:

static void Main(string[] args)
{
    bool a = ((object) new byte[0]) is sbyte[];
    bool b = (new byte[0]) is sbyte[];

    Console.WriteLine(a == b); // False
}

Несоответствие возникает из-за того, что компилятор С# решает, что он знает результат (new byte[0]) is sbyte[] во время компиляции и просто заменяет false. Возможно, это должно действительно заменить true, чтобы быть более совместимым с поведением CLR.

Насколько я могу судить, только эта небольшая оптимизация непоследовательна. Это происходит только тогда, когда обе стороны выражения is статически типизируются как массив, тип элемента которого представляет собой подписанное или беззнаковое целое или перечисление, а размеры целых чисел одинаковы.

Хорошая новость заключается в том, что, хотя это может показаться непоследовательным, С# всегда выдаст предупреждение, когда он заменит false на такие выражения - на практике, я думаю, что это может быть более полезно, чем тихое возвращение true.