С# covariant возвращаемые типы, использующие дженерики

Является ли код ниже единственным способом реализации ковариантных типов возврата?

public abstract class BaseApplication<T> {
    public T Employee{ get; set; }
}

public class Application : BaseApplication<ExistingEmployee> {}

public class NewApplication : BaseApplication<NewEmployee> {}

Я хочу иметь возможность создать приложение или NewApplication и вернуть ему соответствующий тип Employee из свойства Employee.

var app = new Application();
var employee = app.Employee; // this should be of type ExistingEmployee

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

Есть ли другие способы реализации этого поведения? Дженерики или иначе?

Ответ 1

Во-первых, ответ на ваш вопрос - нет, С# не поддерживает ковариантность типа возвращаемого типа в виртуальных переопределениях.

Несколько респондентов и комментаторов сказали: "в этом вопросе нет ковариантности". Это неверно; первоначальный плакат был совершенно прав, чтобы задать вопрос так же, как и они.

Напомним, что ковариантное отображение является отображением, которое сохраняет существование и направление некоторого другого отношения. Например, сопоставление типа T с типом IEnumerable<T> является ковариантным, поскольку оно сохраняет отношение совместимости присваивания. Если Tiger является присвоением, совместимым с Animal, то также сохраняется преобразование под картой: IEnumerable<Tiger> - это присвоение, совместимое с IEnumerable<Animal>.

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

class B
{
    public virtual Animal M() {...}
}
class D : B
{
    public override Tiger M() {...}
}

Tiger совместим с Animal. Теперь сделайте сопоставление от типа T к методу "public T M()". Сохраняет ли это сопоставление совместимость? То есть, если Тигр совместит с животными для целей присвоения, то есть public Tiger M() совместит с public Animal M() для целей виртуального переопределения?

Ответ на С# - "нет". С# не поддерживает такую ​​ковариацию.

Теперь, когда мы установили, что вопрос задан с использованием правильного жаргона алгебры типов, еще несколько мыслей по актуальному вопросу. Очевидная первая проблема заключается в том, что свойство даже не объявлено как виртуальное, поэтому вопросы виртуальной совместимости являются спорными. Очевидная вторая проблема заключается в том, что "get; set;" свойство не может быть ковариантны даже если С# сделал поддержку возвращения типа ковариации, так как тип собственности с легавой не только его тип возврата, также его формальный тип параметра. Для обеспечения безопасности типа вам нужна контравариантность формальных типов параметров. Если мы допустили ковариацию возвращаемого типа по свойствам с сеттерами, тогда у вас будет:

class B
{
    public virtual Animal Animal{ get; set;}
}
class D : B
{
    public override Tiger Animal { ... }
}

B b = new D();
b.Animal = new Giraffe();

и эй, мы просто передали Жирафа сеттера, ожидающего Тигра. Если бы мы поддерживали эту функцию, нам пришлось бы ограничивать ее возвращаемыми типами (как и с ковариацией совместимости с назначаемостью на общих интерфейсах.)

Третья проблема заключается в том, что CLR не поддерживает такой дисперсии; если бы мы хотели поддержать его на языке (как я полагаю, управляемый С++ делает), тогда нам нужно будет сделать некоторые разумно героические меры для работы с ограничениями подписи в CLR.

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

abstract class B 
{
    protected abstract Animal ProtectedM();
    public Animal Animal { get { return this.ProtectedM(); } }
}
class D : B
{
    protected override Animal ProtectedM() { return new Tiger(); }
    public new Tiger Animal { get { return (Tiger)this.ProtectedM(); } }
}

Теперь, если у вас есть экземпляр D, вы видите свойство Тигра. Если вы отбросите его в B, вы увидите свойство, типичное для животных. В любом случае вы по-прежнему получаете виртуальное поведение через защищенный элемент.

Короче говоря, у нас нет планов когда-либо делать эту функцию, извините.

Ответ 2

У вас может быть много проблем с тем, что вы пытаетесь достичь.

Прежде всего, как уже заметил кто-то, в вашем примере нет ковариации. Здесь вы можете найти краткое описание ковариации и дженериков, новые функции в С# 2.0 - разброс, ковариация по дженерикам.

Во-вторых, кажется, что вы пытаетесь решить с помощью дженериков то, что нужно решить с помощью полиморфизма. Если оба ExistingEmployee и NewEmployee наследуются от базового класса Employee, ваша проблема будет решена:

public class Application {
    public ExistingEmployee Employee { get; }
}

public class NewApplication {
    public NewEmployee Employee { get; }
}

...

Application app = new Application;
var emp = app.Employee; // this will be of type ExistingEmployee!

Обратите внимание, что приведенное ниже также верно:

Employee emp = app.Employee; // this will be of type ExistingEmployee even if 
                             // declared as Employee because of polymorphism

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

ExistingEmployee emp = (ExistingEmployee)app.Employee;  // would have not been needed 
                                                        // if working with generics

Надеюсь, что это поможет.

Ответ 3

Вы можете использовать код Employee Interface для получения того, что вы хотите, я думаю.

public interface IEmployee
{}

public abstract class BaseApplication<T> where T:IEmployee{ 
    public T IEmployee{ get; set; } 
} 

public class ExistingEmployee : IEmployee {}
public class NewEmployee : IEmployee {}

public class Application : BaseApplication<ExistingEmployee> {} 

public class NewApplication : BaseApplication<NewEmployee> {} 

Ответ 4

Код, который вы отправили, не будет компилироваться, но я получаю основное представление о том, что вы хотите сделать. Короче говоря, да, это единственный способ. Если вы хотите, чтобы свойство возвращало разные типы и вводилось по-разному в расширенных классах, вам нужно использовать дженерики так, как вы уже есть.

Если вы можете инкапсулировать публичный контракт объекта-сотрудника, нового или существующего, в интерфейс, то вам вообще не нужно использовать дженерики. Вместо этого вы можете просто вернуть интерфейс и разрешить полиморфизм.

public interface IEmployee
{ }

public class Employee1 : IEmployee
{ }

public class Employee2 : IEmployee
{ }

public abstract class ApplicationBase
{
    public abstract IEmployee Employee { get; set; }
}

public class App1 : ApplicationBase
{
    public override IEmployee Employee
    {
        get { return new Employee1(); }
        set;
    }
}

public class App2 : ApplicationBase
{
    public override IEmployee Employee
    {
        get { return new Employee2(); }
        set;
    }
}

Ответ 5

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

Ковариантные типы возврата не, поддерживаемые С#. Так что это не решение, однако, я чувствую, что синтаксически это хорошо читается. Он достигает аналогичного результата.

Мне пригодится при создании fluent API's, где базовому классу необходимо выполнить некоторые действия, но мне нужна производная реализация. Все, что он действительно добивается, - это скрыть приведение.

public class Base
{
    public virtual T Foo<T>() where T : Base 
    { 
        //... // do stuff
        return (T)this; 
    }
}

public class A : Base
{
    public A Bar() { "Bar".Dump(); return this; }
    public A Baz() { "Baz".Dump(); return this; }

    // optionally override the base...
    public override T Foo<T>() { "Foo".Dump(); return base.Foo<T>(); }
}

var x = new A()
    .Bar()
    .Foo<A>() // cast back to A
    .Baz();

Мнения будут меняться, и это не на 100% красиво. Вероятно, это не подходит для API, который будет опубликован, но для внутреннего использования, например, в модульных тестах, я считаю это полезным.

Ответ 6

ДА!! Как это. Существует больше котельной плиты, чем вы надеялись, но она работает. Трюк выполняется с помощью методов расширения. Он дозирует некоторое неприятное литье изнутри, но представляет собой ковариантный интерфейс.

Смотрите также: http://richarddelorenzi.wordpress.com/2011/03/25/return-type-co-variance-in-c/

using System;

namespace return_type_covariance
{
    public interface A1{} 
    public class A2 : A1{}
    public class A3 : A1{}

    public interface B1 
    {
        A1 theA();
    }

    public class B2 : B1
    {
        public A1 theA()
        {
            return new A2();
        }
    }

    public static class B2_ReturnTypeCovariance
    {
        public static A2 theA_safe(this B2 b)
        {
            return b.theA() as A2;    
        }
    }

    public class B3 : B1
    {
        public A1 theA()
        {
            return new A3();    
        }
    }

    public static class B3_ReturnTypeCovariance
    {
        public static A3 theA_safe(this B3 b)
        {
            return b.theA() as A3;    
        }
    }

    public class C2
    {
        public void doSomething(A2 a){}    
    }

    class MainClass
    {
        public static void Main (string[] args)
        {
            var c2 = new C2();
            var b2 = new B2();
            var a2=b2.theA_safe();

            c2.doSomething(a2);
        }
    }
}

Ответ 7

Одна идея без дженериков, но она имеет и другие недостатки:

public abstract class BaseApplication {
 public Employee Employee{ get; protected set; }
}

public class Application : BaseApplication
{
 public new ExistingEmployee Employee{ get{return (ExistingEmployee)base.Employee;} set{base.Employee=value; }}
}

public class NewApplication : BaseApplication
{
 public new NewEmployee Employee{ get{return (NewEmployee)base.Employee;} set{base.Employee=value; }}
}

В частности, с помощью этого кода вы можете применить к базовому классу и назначить сотрудника нежелательного типа. Таким образом, вам нужно добавить проверки в настройку базового класса. Или удалите установщик, который я обычно предпочитаю в любом случае. один из способов сделать это - сделать защитный щит.
Другой - добавление виртуальной функции EmployeeType(), которую вы переопределяете в производных классах и возвращаете производный тип. Затем вы проверяете установщик, если EmployeeType().IsInstanceOf(value), а затем генерируете исключение.

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