Ошибка компилятора C #? Почему это неявное пользовательское преобразование не компилируется? - PullRequest
32 голосов
/ 30 июля 2009

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

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

Ответы [ 2 ]

30 голосов
/ 30 июля 2009

Очевидно, неявные пользовательские преобразования не работают, когда один из типов является интерфейсом. Из спецификации C #:


6.4.1 Разрешенные пользователем преобразования

C # разрешает объявлять только определенные пользователем преобразования. В частности, невозможно переопределить уже существующее неявное или явное преобразование. Для данного типа источника S и целевого типа T, если S или T являются обнуляемыми типами, пусть S0 и T0 ссылаются на их базовые типы, в противном случае S0 и T0 равны S и T соответственно. Классу или структуре разрешается объявлять преобразование из исходного типа S в целевой тип T, только если выполняются все следующие условия:

  • S0 и T0 - это разные типы.
  • S0 или T0 - это класс или тип структуры, в которых происходит объявление оператора.
  • Ни S0, ни T0 не являются интерфейсами .
  • За исключением пользовательских преобразований, преобразование не существует из S в T или из T в S.

В вашем первом методе оба типа не являются типами интерфейса, поэтому неявное преобразование, определенное пользователем, работает.

Спецификации не очень понятны, но мне кажется, что если один из задействованных типов является типом интерфейса, компилятор даже не пытается искать какие-либо неявные пользовательские преобразования.

24 голосов
/ 31 июля 2009

(отслеживание комментариев принятого ответа.)

Да, это очень, очень запутанная часть спецификации. Все о «охватывающих типах», в частности, глубоко ошибочно. Уже несколько лет я пытаюсь найти время, чтобы полностью переписать весь этот раздел в нечто более связное, но это никогда не было достаточно высоким приоритетом.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...