Ошибка компилятора С#? Почему это неявное пользовательское преобразование не компилируется?
Учитывая следующую структуру:
public struct Foo<T>
{
public Foo(T obj) { }
public static implicit operator Foo<T>(T input)
{
return new Foo<T>(input);
}
}
Этот код компилируется:
private Foo<ICloneable> MakeFoo()
{
string c = "hello";
return c; // Success: string is ICloneable, ICloneable implicitly converted to Foo<ICloneable>
}
Но этот код не компилируется - почему?
private Foo<ICloneable> MakeFoo()
{
ICloneable c = "hello";
return c; // Error: ICloneable can't be converted to Foo<ICloneable>. WTH?
}
Ответы
Ответ 1
По-видимому, неявные пользовательские преобразования не работают, когда один из типов является интерфейсом. Из спецификаций С#:
6.4.1 Разрешенные пользовательские преобразования
С# разрешает объявлять только определенные пользовательские преобразования. В частности, невозможно переопределить уже существующее неявное или явное преобразование.
Для данного типа источника S и целевого типа T, если S или T являются типами NULL, пусть S0 и T0 относятся к их базовым типам, в противном случае S0 и T0 равны S и T соответственно. Класс или структура разрешено объявлять преобразование из исходного типа S в целевой тип T только в том случае, если все из следующих значений истинны:
- S0 и T0 - разные типы.
- Либо S0, либо T0 - тип класса или структуры, в котором имеет место объявление оператора.
- Ни S0, ни T0 не являются интерфейсом типа.
- Исключая пользовательские преобразования, преобразование не существует от S до T или от T до S.
В вашем первом методе оба типа не являются типами интерфейсов, поэтому пользовательское неявное преобразование работает.
Спецификации не очень ясны, но мне кажется, что если один из типов связан с типом интерфейса, компилятор даже не пытается найти какие-либо пользовательские неявные преобразования.
Ответ 2
(Следуя комментариям принятого ответа.)
Да, это очень, очень запутанная часть спецификации. Весь бит о "охватывающих типы" в частности глубоко испорчен. Я уже несколько лет пытаюсь найти время, чтобы полностью переписать весь этот раздел во что-то более последовательное, но никогда не было достаточно высокого приоритета.
По сути, мы имеем противоречие; мы говорим, что не существует пользовательских неявных преобразований, включающих интерфейсы, но ясно, что это неверно в этом случае; существует пользовательское неявное преобразование из IC в Foo<IC>
, демонстрируемое тем фактом, что строка переходит на Foo<IC>
через это преобразование.
То, что мы действительно должны лучше подчеркивать, - это строка, которую вы цитировали:
В частности, невозможно переопределить уже существующее неявное или явное преобразование.
То, что мотивирует все это; желание не позволить вам когда-либо думать, что вы выполняете тест типа, сохраняющий представление, когда на самом деле вы вызываете пользовательский метод. Рассмотрим, например, эту вариацию:
interface IBar {}
interface IFoo : IBar {}
class Foo<T> : IFoo
{
public static explicit operator Foo<T>(T input) { whatever }
}
class Blah : Foo<IBar> {}
...
IBar bar = new Blah();
Foo<IBar> foo = (Foo<IBar>)bar;
Теперь, вызывает ли это явное преобразование, определяемое пользователем, или нет? Объект действительно получен из Foo, поэтому вы надеетесь, что этого не произойдет; это должен быть простой тест типа и ссылочное задание, а не вызов вспомогательного метода. Приведение значения интерфейса всегда рассматривается как тест типа, поскольку почти всегда возможно, что объект действительно имеет этот тип и действительно реализует этот интерфейс. Мы не хотим отказывать вам в возможности сделать дешевое преобразование, сохраняющее представление.