Почему этот Linq Cast Fail при использовании ToList?
Рассмотрим этот надуманный, тривиальный пример:
var foo = new byte[] {246, 127};
var bar = foo.Cast<sbyte>();
var baz = new List<sbyte>();
foreach (var sb in bar)
{
baz.Add(sb);
}
foreach (var sb in baz)
{
Console.WriteLine(sb);
}
С магией Two Complement, -10 и 127 печатается на консоли. Все идет нормально. Люди с острыми глазами увидят, что я повторяю перечислимое количество и добавляю его в список. Это звучит как ToList
:
var foo = new byte[] {246, 127};
var bar = foo.Cast<sbyte>();
var baz = bar.ToList();
//Nothing to see here
foreach (var sb in baz)
{
Console.WriteLine(sb);
}
За исключением того, что это не работает. Я получаю это исключение:
Тип исключения: System.ArrayTypeMismatchException
Сообщение: Тип исходного массива не может быть назначен типу целевого массива.
Я нахожу это исключение очень своеобразным, потому что
-
ArrayTypeMismatchException
- Я ничего не делаю с массивами. Это, по-видимому, внутреннее исключение.
-
Cast<sbyte>
работает отлично (как в первом примере), при использовании ToArray
или ToList
проблема возникает.
Я ориентирую .NET v4 x86, но то же самое происходит и в 3.5.
Мне не нужны какие-либо советы о том, как решить проблему, я уже успел это сделать. Я хочу знать, почему это происходит в первую очередь?
ИЗМЕНИТЬ
Даже более странно, добавляя бессмысленный оператор select, оператор ToList
работает правильно:
var baz = bar.Select(x => x).ToList();
Ответы
Ответ 1
Хорошо, это действительно зависит от нескольких странностей:
-
Даже если на С# вы не можете напрямую передать byte[]
в 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[]);
-
Cast<T>()
оптимизирует так, что если он думает, что ему ничего не нужно (с помощью проверки is
, как указано выше), он возвращает исходную ссылку - так что здесь происходит.
/li > -
ToList()
делегирует конструктору List<T>
с помощью IEnumerable<T>
-
Этот конструктор оптимизирован для ICollection<T>
для использования CopyTo
... и это то, что не удается. Здесь версия, не имеющая методов, кроме CopyTo
:
object bytes = new byte[] { 246, 127 };
// This succeeds...
ICollection<sbyte> list = (ICollection<sbyte>) bytes;
sbyte[] array = new sbyte[2];
list.CopyTo(array, 0);
Теперь, если вы используете Select
в любой точке, вы не получите ICollection<T>
, поэтому он проходит через законное (для CLR) преобразование byte
/sbyte
для каждого элемента, вместо того, чтобы пытаться использовать реализацию массива CopyTo
.