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

Почему этот 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();
4b9b3361

Ответ 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.