Универсальный метод может использовать контравариантные / ковариантные типы? - PullRequest
8 голосов
/ 12 ноября 2011

Я пишу обобщенный метод, чтобы использовать его в специальной задаче в шаблоне T4.Метод должен позволять мне использовать специализированные типы из общего интерфейса.Я думал о следующих сигнатурах:

interface IGreatInterface {
    Object aMethodAlpha<U>(U parameter) where U : IAnInterface;
    Object aMethodBeta(IAnInterface parameter)
}

public class AnInterestingClass : IAnInterface{}

Когда я пытаюсь реализовать IGreatInterface, компилятор помечает ошибку для aMethodBeta(), потому что я заставил свой T4 написать этот метод, используя подтип IAnInterface (т.е. я хочу реализовать этот метод следующим образом: Object aMethodBeta(AnInterestingClass parameter)).

Метод aMethodAlpha<U>() можно использовать, но он не так чист, как я хочу, потому что мой T4 должен генерировать некоторый дополнительный код.Я (возможно, ошибочно) предполагаю, что реализация этого метода, которую должен выполнять T4, может быть
Object aMethodAlpha<AnInterestingClass>(AnInterestingClass parameter).

Я думаю, что универсальные методы не поддерживают контравариантные типы, ноЯ не уверен;Я полагаю, что именно так компилятор не позволяет кодеру использовать определенный тип, имеющий метод, не определенный в общем типе ...

  1. Должен ли универсальный метод использовать точный тип при реализации?
  2. Есть ли способ изменить это поведение?

Ответы [ 3 ]

22 голосов
/ 12 ноября 2011

Этот вопрос довольно запутанный.Дайте мне посмотреть, смогу ли я уточнить это.

Когда я пытаюсь реализовать IGreatInterface, компилятор помечает ошибку для aMethodBeta(), потому что я сделал этот метод, используя подтип IAnInterface Iхочу реализовать этот метод следующим образом: Object aMethodBeta(AnInterestingClass parameter).

Это не законно.Упрощенно несколько:

class Food {}
class Fruit : Food {}
class Meat : Food {}
interface IEater
{
    void Eat(Food food);
}
class Vegetarian : IEater
{
    public void Eat(Fruit fruit);
}

Класс Vegetarian не выполняет контракт IEater.Вы должны быть в состоянии передать любую пищу для еды, но Vegetarian принимает только фрукты.C # не поддерживает формальный параметр ковариации виртуального метода , потому что это небезопасно.

Теперь вы можете сказать, как насчет этого:

interface IFruitEater
{
    void Eat(Fruit fruit);
}
class Omnivore : IFruitEater
{
    public void Eat(Food food);
}

Теперь мы имеемприобретенный тип безопасности;Omnivore можно использовать в качестве IFruitEater, поскольку Omnivore может есть фрукты, а также любую другую пищу.

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

Точно так же C # не поддерживает дисперсию типа возвращаемого виртуального метода .

Я не уверен, действительно ли это ответило на ваш вопрос или нет,Можете ли вы уточнить вопрос?

ОБНОВЛЕНИЕ:

Как насчет:

interface IEater
{
    void Eat<T>(T t) where T : Food;
}
class Vegetarian : IEater
{
    // I only want to eat fruit!
    public void Eat<Fruit>(Fruit food) { }
}

Нет, это тоже не законно.Контракт IEater заключается в том, что вы предоставите метод Eat<T>, который может принимать любой T, то есть Food.Вы не можете частично выполнить договор, больше, чем вы могли бы сделать это:

interface IAdder
{
    int Add(int x, int y);
}
class Adder : IAdder
{
    // I only know how to add two!
    public int Add(2, int y){ ... }
}

Однако вы можете сделать это:

interface IEater<T> where T : Food
{
    void Eat(T t);
}
class Vegetarian : IEater<Fruit>
{
    public void Eat(Fruit fruit) { }
}

Это совершенно законно,Тем не менее, вы не можете сделать:

interface IEater<T> where T : Food
{
    void Eat(T t);
}
class Omnivore : IEater<Fruit>
{
    public void Eat(Food food) { }
}

Потому что, опять же, C # не поддерживает виртуальный метод формального параметра контравариантности или ковариации.

Обратите внимание, что C # поддерживает поддержку параметрического полиморфизма ковариации , когда известно, что это безопасно для типов.Например, это законно:

IEnumerable<Fruit> fruit = whatever;
IEnumerable<Food> food = fruit;

Последовательность фруктов может использоваться как последовательность пищи.Или,

IComparable<Fruit> fruitComparer = whatever;
IComparable<Apples> appleComparer = fruitComparer;

Если у вас есть что-то, что может сравнивать любые два фрукта, тогда оно может сравнивать любые два яблока.

Однако такой вид ковариации и контравариантности допустим только тогда, когда всеверно следующее: (1) дисперсия доказуемо безопасна для типов, (2) автор аннотации добавлен тип, указывающий желаемые совместные и противоположные дисперсии, (3) все аргументы переменного типа являются ссылочными типами, (4)универсальный тип является делегатом или интерфейсом.

2 голосов
/ 12 ноября 2011

Если вы хотите наследовать от универсального интерфейса, см. Ответ Phoog. Если вы говорите о попытке реализовать интерфейс по-другому, это приводит к моему обсуждению ниже.

Предположим:

internal interface IAnInterface { }

public class SomeSubClass : IAnInterface { }

public class AnotherSubClass : IAnInterface { }

public GreatClass : IGreatInterface { ... }

Проблема с попыткой реализовать интерфейс с более производным (ко-вариантным) аргументом состоит в том, что нет никакой гарантии, когда он вызывается через интерфейс, что переданный IAnInterface будет экземпляром SomeSubClass. Вот почему не разрешено напрямую.

IGreatInterface x = new GreatClass();

x.aMethodBeta(new AnotherSubClass());

ЕСЛИ Вы могли бы сделать ковариацию, это потерпит неудачу, потому что вы ожидаете SomeSubClass, но получите AnotherSubClass.

Что вы могли бы сделать, это сделать явную реализацию интерфейса:

class GreatInterface : IGreatInterface
{
    // explicitly implement aMethodBeta() when called from interface reference
    object IGreatInterface.aMethodBeta(IAnInterface parameter)
    {
        // do whatever you'd do on IAnInterface itself...
        var newParam = parameter as SomeSubClass;

        if (newParam != null)
        {
            aMethodBeta(newParam);
        }

        // otherwise do some other action...
    }

    // This version is visible from the class reference itself and has the 
    // sub-class parameter
    public object aMethodBeta(SomeSubClass parameter)
    {
        // do whatever
    }
}

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

ОБНОВЛЕНИЕ : звучит так, будто вы хотите что-то вроде этого:

public interface ISomeInterface
{
    void SomeMethod<A>(A someArgument);
}

public class SomeClass : ISomeInterface
{
    public void SomeMethod<TA>(TA someArgument) where TA : SomeClass
    {

    }
}

Это недопустимо, когда вы реализуете универсальный метод из интерфейса, ограничения должны совпадать.

0 голосов
/ 12 ноября 2011

Может быть, вы ищете это:

interface IGreatInterface<in U> where U : IAnInterface
{ 
    Object aMethodAlpha(U parameter);
} 

class SomeClass : IAnInterface { /*...*/ }

class GreatClass : IGreatInterface<SomeClass>
{
    public Object aMethodAlpha(SomeClass parameter) {}
}

EDIT:

Да, вы правы: если вы определяете универсальный метод в интерфейсе, вы не можете реализовать этот метод с конкретным методом, использующим совместимый тип.

Как насчет использования делегата (поскольку делегаты поддерживают ко-и контравариантность):

[пример удален, потому что я получил дисперсию назад - она ​​не работает.]

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