Почему мой массив С# теряет информацию о знаке типа при передаче на объект?

Исследуя ошибку, я обнаружил, что это связано с этой странностью в С#:

sbyte[] foo = new sbyte[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}",
        foo is sbyte[], foo is byte[], bar is sbyte[], bar is byte[]);

Вывод "True False True True", в то время как я ожидал бы "bar is byte[]" для возврата False. Очевидно, что бар является a byte[] и a sbyte[]? То же самое происходит и для других типов подписанных/неподписанных типов, таких как Int32[] vs UInt32[], но не для слова Int32[] vs Int64[].

Может ли кто-нибудь объяснить это поведение? Это в .NET 3.5.

Ответ 1

UPDATE: я использовал этот вопрос в качестве основы для записи в блоге, здесь:

http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx

См. комментарии к блогу для расширенного обсуждения этой проблемы. Спасибо за отличный вопрос!


Вы столкнулись с интересной и неудачной несогласованностью между системой типа CLI и системой типа С#.

У CLI есть понятие "совместимость присваивания". Если значение x известного типа данных S "совместимо с назначением" с определенным местом хранения y известного типа данных T, вы можете сохранить x в y. Если нет, то сделать это не проверяемый код, и верификатор запретит его.

Система типа CLI говорит, например, что подтипы ссылочного типа являются присвоением, совместимым с супертипами ссылочного типа. Если у вас есть строка, вы можете сохранить ее в переменной типа объекта, потому что оба являются ссылочными типами, а строка - подтипом объекта. Но противоположное не верно; супертипы не являются совместимыми с подтипами. Вы не можете вставлять что-то, что известно только как объект в переменную типа string, без его литья.

В принципе "совместимое с присвоением" означает "имеет смысл вставлять эти точные биты в эту переменную". Назначение из исходного значения в целевую переменную должно быть "сохранением представления". Подробнее см. В моей статье:

http://ericlippert.com/2009/03/03/representation-and-identity/

Одним из правил CLI является "если X является присвоением, совместимым с Y, то X [] является присвоением, совместимым с Y []".

То есть массивы являются ковариантными относительно совместимости присваивания. Это на самом деле некая ковариация; более подробную информацию см. в моей статье.

http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

Это НЕ правило С#. Правило ковариации массива С# является "если X является ссылочным типом, неявно преобразованным в ссылочный тип Y, то X [] неявно конвертируется в Y []". Это тонкое другое правило и, следовательно, ваша запутанная ситуация.

В CLI uint и int совместимы с назначением. Но в С# преобразование между int и uint является EXPLICIT, а не IMPLICIT, и это типы значений, а не ссылочные типы. Таким образом, в С# это не является законным для преобразования int [] в uint [].

Но это законно в CLI. Итак, теперь у нас есть выбор.

1) Реализация "есть", так что, когда компилятор не может определить ответ статически, он фактически вызывает метод, который проверяет все правила С# для конвертируемости, сохраняющей идентичность. Это медленно, и 99,9% времени соответствует правилам CLR. Но мы принимаем удар производительности, чтобы быть на 100% совместимым с правилами С#.

2) Реализация "есть", так что, когда компилятор не может определить ответ статически, он выполняет невероятно быструю проверку совместимости назначений CLR и живет с тем, что это говорит о том, что uint [] является int [], даже хотя это фактически не было бы законным в С#.

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

Ответ 2

Отредактируйте фрагмент с помощью Reflector:

sbyte[] foo = new sbyte[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}", new object[] { foo != null, false, bar is sbyte[], bar is byte[] });

Компилятор С# оптимизирует первые два сравнения (foo is sbyte[] и foo is byte[]). Как вы видите, они оптимизированы для foo != null и просто всегда false.

Ответ 3

Также интересно:

    sbyte[] foo = new sbyte[] { -1 };
    var x = foo as byte[];    // doesn't compile
    object bar = foo;
    var f = bar as byte[];    // succeeds
    var g = f[0];             // g = 255

Ответ 4

Конечно, выход правильный. bar "is" и sbyte [] и byte [], потому что он совместим с обоими, поскольку bar является просто объектом, тогда он "может быть" либо подписанным, либо без знака.

"is" определяется как выражение может быть отлито для типа.