Ковариация возвращаемого типа C# и принцип подстановки Лискова

Я пытаюсь понять ковариацию и LSP. Из этот вопрос я вижу, что С# не поддерживает ковариантность возвращаемого типа. Однако принцип подстановки Лисков накладывает ковариантность на возвращаемый тип.

Значит ли это, что этот принцип невозможно применить в C#? Или я что-то не так понял?


person Ythio Csi    schedule 10.05.2017    source источник
comment
Ты не понимаешь. Ковариантность возвращаемого типа — это не то же самое, что возвращаемое значение является ковариантным. Это просто означает, что вы не можете переопределить метод с другим типом возвращаемого значения. По-прежнему совершенно нормально возвращать экземпляр производного типа из метода, который возвращает базовый тип — производный тип по-прежнему является подтипом базового типа; LSP до сих пор подходит.   -  person Luaan    schedule 10.05.2017


Ответы (3)


C# по-прежнему может применять принцип подстановки Лискова.

Рассмотреть возможность:

public class Base1
{
}

public class Derived1 : Base1
{
}

public class Base2
{
    public virtual Base1 Method()
    {
        return new Base1();
    }
}

public class Derived2 : Base2
{
    public override Base1 Method()
    {
        return new Derived1();
    }
}

Если бы C# поддерживал ковариантные возвращаемые типы, то переопределение для Method() в Base2 можно было бы объявить следующим образом:

public class Derived2 : Base2
{
    public override Derived1 Method()
    {
        return new Derived1();
    }
}

C# этого не позволяет, и вы должны объявить возвращаемый тип таким же, как и в базовом классе, а именно Base1.

Однако это не означает нарушения принципа замещения Лискова.

Учти это:

Base2 test = new Base2();
Base1 item = test.Method();

По сравнению с:

Base2 test = new Derived2();
Base1 item = test.Method();

Мы вполне можем без проблем заменить new Base2() на new Derived2(). Это соответствует принципу замещения Лискова.

person Matthew Watson    schedule 10.05.2017

Поскольку ковариантность возвращаемого типа не поддерживается в C#, невозможно нарушить принцип Лискова, когда речь идет о ковариантности возвращаемого типа.

Хорошим источником является это обсуждение принципов S.O.L.I.D в C#: https://youtu.be/gwIS9cZlrhk?t=1886

person bbartels    schedule 10.05.2017

C# имеет ограниченную поддержку этой функции с помощью дженериков и универсального модификатора out. (См.: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-generic-modifier)

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

public class Base1
{
}

public class Derived1 : Base1
{
}

public interface Base2<out T> where T : Base1
{
    T Method();
}

public class Derived2 : Base2<Derived1>
{
    public Derived1 Method()
    {
        return new Derived1();
    }
}

В этом случае Derived2 будет реализовывать не только Base2<Derived1>, но и Base2<Base1>.

Принцип вступает в силу, потому что если вы вызовете Method через интерфейс Base2<Base1>, он будет иметь тип возврата Base1, но если вы вызовете его через Derived2, он будет иметь тип возврата Derived1.

Точно так же вы можете реализовать контравариантность параметров с помощью универсального модификатора in: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-generic-modifier

Обратите внимание, что нарушать принцип невозможно. Если в приведенном выше примере вы измените ключевое слово out на ключевое слово in, исходный код не будет скомпилирован, и вы получите следующую ошибку:

Error   CS1961  Invalid variance: The type parameter 'T' must be covariantly valid on 'Base2<T>.Method()'. 'T' is contravariant.
person Gábor Angyal    schedule 10.12.2017
comment
Я думаю, что правильная фраза должна работать только с универсальными интерфейсами, поскольку она не работает с неуниверсальными интерфейсами. - person Vitaly; 06.05.2019