Почему помощник Linq Cast <> не работает с неявным оператором литья?

Пожалуйста, прочитайте до конца, прежде чем принимать решение о голосовании как дубликат...

У меня есть тип, который реализует оператор implicit cast для другого типа:

class A
{
    private B b;
    public static implicit operator B(A a) { return a.b; }
}
class B
{
}

Теперь неявное и явное кастинг просто отлично:

B b = a;
B b2 = (B)a;

... так как же Linq .Cast<> нет?

A[] aa = new A[]{...};
var bb = aa.Cast<B>();  //throws InvalidCastException

Глядя на исходный код для .Cast<>, магии не происходит: несколько особых случаев, если параметр действительно является IEnumerable<B>, а затем:

foreach (object obj in source) 
    yield return (T)obj; 
    //            ^^ this looks quite similar to the above B b2 = (B)a;

Итак, почему мой явный литье работает, но не внутри .Cast<>?

Скомпилирует ли компилятор мой явный листинг?

PS. Я видел этот вопрос, но я не думаю, что его ответы действительно объясняют, что происходит.

Ответ 1

Короткий ответ будет просто: метод Cast<T> не поддерживает настраиваемые операторы преобразования.

В первом примере:

B b = a;
B b2 = (B)a;

компилятор может видеть этот оператор B(A a) во время статического анализа; компилятор интерпретирует это как статический call для вашего пользовательского метода оператора. Во втором примере:

foreach (object obj in source) 
    yield return (T)obj; 

который не знает оператора; это реализуется через unbox.any (что совпадает с castclass, если T является ref-типом).

Существует также третий вариант: если вы прошли через dynamic, реализация выполнения пытается имитировать правила компилятора, так что это найдет оператор... но не как часть этапа компиляции С# -to-IL:

dynamic b = a; // note that `dynamic` here is *almost* the same as `object`
B b2 = b;

Ответ 2

Итак, почему моя явная работа над литьем, а внутри внутри .Cast < > нет?

Явное высказывание во время компиляции знает, что такое тип источника и назначения. Компилятор может определить явное преобразование и испустить код для его вызова.

Это не относится к родовым типам. Обратите внимание, что это не относится к Cast или LINQ вообще - вы увидите то же самое, если попробуете простой метод Convert:

public static TTarget Convert<TSource, TTarget>(TSource value)
{
    return (TTarget) value;
}

Это не будет вызывать любые пользовательские преобразования - или даже преобразования из (скажем) int в long. Он будет выполнять только преобразования ссылок и преобразования бокса/распаковки. Это просто часть того, как работают дженерики.

Ответ 3

Enumerable.Cast<T> - это .NET-метод и имеет поведение, которое имеет смысл во всех языках .Net, которые его называют.

См. также, Андер Хейлсберг ответил на эту дискуссию.


Скомпилирует ли компилятор мой явный листинг?

То, что вы называете "неявным оператором литья", на самом деле является " неявным оператором преобразования". Это распространенная ошибка.

С# позволяет вам указывать преобразования с использованием синтаксиса каста. Когда это происходит, вы используете другой экземпляр (преобразование), не изменяя ссылку на тот же экземпляр (кастинг).