Я понимаю, что это запрещено, потому что есть правила против этого. Я не понимаю, почему это не разрешено.
Общее правило: определенное пользователем преобразование никоим образом не должно заменять встроенное преобразование. Существуют тонкие способы нарушить это правило, используя универсальные типы, но вы, в частности, говорите, что вы не заинтересованы в сценариях универсального типа.
Вы не можете, например, выполнить пользовательское преобразование из MyClass
в Object
, поскольку уже является неявным преобразованием из MyClass
в Object
. «Встроенное» преобразование будет всегда выигрывать , поэтому бессмысленно объявлять пользовательское преобразование.
Более того, вы даже не можете сделать пользовательское неявное преобразование, которое заменяет встроенное явное преобразование. Вы не можете, например, сделать пользовательское неявное преобразование из Object
в MyClass
, потому что уже есть встроенное явное преобразование из Object
в MyClass
. Для читателя кода это слишком запутанно, чтобы позволить вам произвольно реклассифицировать существующие явные преобразования как неявные преобразования.
Это особенно случай, когда идентичность . Если я скажу:
object someObject = new MyClass();
MyClass myclass = (MyClass) someObject;
тогда я ожидаю, что это означает, что "someObject
на самом деле имеет тип MyClass
, это явное преобразование ссылок, и теперь myclass
и someObject
равны ссылкам". Если бы вам было позволено сказать
public static implicit operator MyClass(object o) { return new MyClass(); }
тогда
object someObject = new MyClass();
MyClass myclass = someObject;
будет допустимым , а два объекта не будут иметь ссылочного равенства , что странно .
У нас уже есть достаточно правил, чтобы дисквалифицировать ваш код, который преобразуется из интерфейса в открытый тип класса. Учтите следующее:
class Foo { }
class Foo2 : Foo, IBlah { }
...
IBlah blah = new Foo2();
Foo foo = (Foo) blah;
Это работает, и можно разумно ожидать, что blah
и foo
являются ссылочными равными, потому что приведение Foo2 к его базовому типу Foo не меняет ссылку. Теперь предположим, что это законно:
class Foo
{
public static implicit operator Foo(IBlah blah) { return new Foo(); }
}
Если это законно, то этот код является законным:
IBlah blah = new Foo2();
Foo foo = blah;
мы только что преобразовали экземпляр производного класса в его базовый класс, но они не равны ссылкам. Это странно и запутанно, и поэтому мы делаем его незаконным. Вы просто не можете объявить такое неявное преобразование , поскольку оно заменяет существующее встроенное явное преобразование.
Таким образом, правило, что вы не должны заменять любое встроенное преобразование каким-либо определенным пользователем преобразованием, является достаточным , чтобы лишить вас возможности создавать преобразование, которое принимает интерфейс.
Но подождите! Предположим, что Foo
является запечатанным . Тогда - это без преобразования между IBlah
и Foo
, явным или неявным, потому что не может быть возможно производным Foo2
, который реализует IBlah
. В этом случае мы должны разрешить пользовательское преобразование между Foo
и IBlah
? Такое пользовательское преобразование не может заменить любое встроенное преобразование, явное или неявное.
Нет. В разделе 10.10.3 спецификации добавлено дополнительное правило, которое явно запрещает любое пользовательское преобразование в интерфейс или из интерфейса независимо от того, заменяет ли оно встроенное преобразование или нет.
Почему? Поскольку есть разумное ожидание, что когда кто-то преобразует значение в интерфейс, то вы проверяете, реализует ли рассматриваемый объект интерфейс , а не , запрашивая совершенно другое объект, который реализует интерфейс. В терминах COM преобразование в интерфейс QueryInterface
- " реализуете ли вы этот интерфейс? " - а не QueryService
- " можете ли вы найти кого-то, кто реализует этот интерфейс?"
Точно так же есть разумное ожидание, что когда кто-то преобразует из интерфейса, каждый спрашивает, действительно ли интерфейс реализован объектом данного целевого типа , а не запрос объекта целевого типа, который полностью отличается от объекта, который реализует интерфейс.
Таким образом, всегда незаконно делать пользовательское преобразование, которое преобразует в или изинтерфейс.
Тем не менее, генерики значительно мутят воду, формулировка спецификации не очень ясна, и компилятор C # содержит ряд ошибок в его реализации .Ни спецификация, ни реализация не являются правильными, учитывая определенные крайние случаи, связанные с дженериками, и это представляет сложную проблему для меня, для разработчика.Сегодня я работаю с Мэдсом над уточнением этого раздела спецификации, так как я реализую его в Рослине на следующей неделе.Я попытаюсь сделать это с минимальным количеством возможных изменений, но может потребоваться небольшое количество, чтобы привести поведение компилятора и язык спецификации в соответствие друг с другом.