Можете ли вы удовлетворить общее ограничение с помощью неявного преобразования?

Учитывая эти типы:

class A { }
class B
{
    public static implicit operator A(B me)
    {
        return new A();
    }
}

class Test<T> where T : A { }

Я старался

var b = new Test<B>();

И ожидал провала, что и произошло. Но сообщение об ошибке

Тип «B» нельзя использовать в качестве параметра типа «T» в универсальном типе или методе «Test». Нет неявного преобразования ссылки из «B» в «A».

Но есть существует неявное преобразование ссылки из B в A. Это просто странное сообщение? Не существует не неявного преобразования ссылки, как у Адама Робинсона показывает ответ. Сообщение правильное.

Обратите внимание, что MSDN говорит:

где T : (имя базового класса) — Аргумент типа должен быть или производным от указанного базового класса.

Это объясняет, почему это не разрешено, поскольку B не происходит от A.


person default.kramer    schedule 17.11.2011    source источник


Ответы (4)


Нет, то, что вы пытаетесь сделать, невозможно. Неявное преобразование ссылки — это не то же самое, что неявное преобразование типа. Ваш код определяет неявное преобразование типов, где вы можете сделать следующее:

B foo = new B();
A bar = foo;

Обратите внимание, однако, что foo и bar теперь содержат разные ссылки. Неявное преобразование типов создает новый экземпляр A, который должен быть (по соглашению) логически эквивалентен foo. Но дело в том, что это другая ссылка.

При преобразовании ссылки сама ссылка не изменяется, что означает, что рассматриваемый тип должен либо наследовать (для классов), либо реализовывать (для интерфейсов) рассматриваемый тип. Если я сделаю это:

class A { }
class B : A { }

Тогда мой код выше теперь будет содержать одну и ту же ссылку в foo и bar. Это то, что подразумевается под неявным преобразованием ссылки. И наоборот, явное преобразование ссылок будет понижающим, например:

A foo = new B();
B bar = (B)foo;

Опять же, ссылки те же, но актерский состав был явным.

Короче говоря, документация MSDN понятнее, но менее точна.

person Adam Robinson    schedule 17.11.2011
comment
+1 Я не понял разницы между преобразованием типа и преобразованием ссылки. - person default.kramer; 17.11.2011
comment
Я бы хотел, чтобы у нас были общие ограничения, позволяющие нам указывать необходимые преобразования — это помогло бы при использовании композиции вместо наследования, хотя нам все еще нужен какой-то способ разрешить неявные преобразования в (и из) интерфейсов с преобразованием типа, а не ссылка-преобразование. Ну что ж. - person Dai; 29.03.2021

Это невозможно.

Неявное преобразование отличается от эквивалентности типов. Тот факт, что тип может быть преобразован в другой тип, не означает, что он является конкретной формой второго типа. Таким образом, он не работает для общих ограничений.

Это имеет смысл - подумайте, что компилятор сделал бы в следующем:

class A 
{
    public void Foo();
}
class B
{
    public static implicit operator A(B me)
    {
        return new A();
    }
}

Теперь, скажем, у вас есть:

public void Bar<T>(T obj) where T : A
{       
    obj.Foo();
    obj.Foo();
    obj.Foo();
}

Чтобы это работало с преобразованиями (т.е. разрешил вызов Bar(new B())) - вам нужно было бы создать НОВЫЙ экземпляр объекта внутри этого метода, так как Foo не определен в B. Это было бы очень неожиданно и могло привести к некоторые очень трудно обнаружить ошибки. В приведенном выше примере должна ли операция преобразования выполняться при каждом вызове метода? Должно ли это произойти один раз, а компилятор придумает какой-нибудь трюк, чтобы заставить его работать? Хотя можно представить себе способы справиться с этим, ни один из них не ясен...

person Reed Copsey    schedule 17.11.2011

Другие люди освещали это в основном, но я подумал, что вставлю некоторые спецификации

Полный список того, что считается допустимым, находится в главе 6.1.6 спецификации языка С#. Ключевой частью является последний абзац, в котором говорится:

Преобразования ссылок, неявные или явные, никогда не изменяют ссылочную идентичность преобразуемого объекта. Другими словами, хотя преобразование ссылки может изменить тип ссылки, оно никогда не изменяет тип или значение объекта, на который делается ссылка.

Полный список преобразований выглядит следующим образом:

Неявные преобразования ссылок:

  • От любого ссылочного типа к объектному и динамическому.
  • От любого типа класса S к любому типу класса T при условии, что S является производным от T.
  • От любого типа класса S до любого типа интерфейса T при условии, что S реализует T.
  • От любого типа интерфейса S к любому типу интерфейса T при условии, что S является производным от T.
  • From an array-type S with an element type SE to an array-type T with an element type TE, provided all of the following are true:
    • S and T differ only in element type. In other words, S and T have the same number of dimensions.
    • И SE, и TE являются ссылочными типами.
    • Существует неявное преобразование ссылок из SE в TE.
  • От любого типа массива до System.Array и интерфейсов, которые он реализует.
  • От типа одномерного массива S[] к System.Collections.Generic.IList и его базовым интерфейсам при условии, что существует неявное преобразование идентификатора или ссылки из S в T.
  • От любого типа делегата до System.Delegate и интерфейсов, которые он реализует.
  • От нулевого литерала до любого ссылочного типа.
  • От любого ссылочного типа к ссылочному типу T, если он имеет неявную идентичность или преобразование ссылки к ссылочному типу T0, а T0 имеет преобразование идентичности к T.
  • От любого ссылочного типа к интерфейсу или типу делегата T, если он имеет неявное преобразование идентификатора или ссылки к интерфейсу или типу делегата T0, и T0 преобразуется с помощью дисперсии (§13.1.3.2) в T.
  • Неявные преобразования с использованием параметров типа, которые известны как ссылочные типы. См. §6.1.10 для более подробной информации о неявных преобразованиях с использованием параметров типа.
person Chris    schedule 17.11.2011

В вашем примере кода B не является производным от A. Пытаться

class B : A
{ 
    public static implicit operator A(B me) 
    { 
        return new A(); 
    } 
} 

Нет, это не странное сообщение. «Неявное преобразование ссылок» (раздел 6.1.6. Спецификации) — это не то же самое, что «определяемое пользователем неявное преобразование» (раздел 6.1.10), которое у вас есть.

Преобразование ссылки означает, что вы можете преобразовать ссылку на данный объект из одного типа в другой ("преобразование ссылки... никогда не изменяет ссылочную идентичность преобразуемого объекта").

Определяемое пользователем неявное преобразование может (как и ваше) возвращать новый, другой объект.

person phoog    schedule 17.11.2011
comment
Э-э ... это все равно не сработает (ОП явно избегает наследования в своем образце), и если этот код является законным, его следует вернуть и расстрелять. - person Adam Robinson; 17.11.2011
comment
@AdamRobinson Я еще не опубликовал правку, когда вы написали свой предыдущий комментарий. - person phoog; 17.11.2011
comment
@phoog: Вероятно, вам следует опубликовать редактирование перед комментарием, отсылающим к нему людей, поскольку я определенно думал так же, как Адам. - person Chris; 17.11.2011