С# 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
. Он возвращает то же, что и базовый класс, и просто добавляет дополнительные гарантии к контракту функции.