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

Ошибка компилятора С#? Почему это неявное пользовательское преобразование не компилируется?

Учитывая следующую структуру:

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?
}
4b9b3361

Ответ 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, поэтому вы надеетесь, что этого не произойдет; это должен быть простой тест типа и ссылочное задание, а не вызов вспомогательного метода. Приведение значения интерфейса всегда рассматривается как тест типа, поскольку почти всегда возможно, что объект действительно имеет этот тип и действительно реализует этот интерфейс. Мы не хотим отказывать вам в возможности сделать дешевое преобразование, сохраняющее представление.