使用generics的c#协变返回types

下面的代码是实现协变返回types的唯一方法吗?

public abstract class BaseApplication<T> { public T Employee{ get; set; } } public class Application : BaseApplication<ExistingEmployee> {} public class NewApplication : BaseApplication<NewEmployee> {} 

我希望能够构build一个应用程序或一个新的应用程序,并从Employee属性返回相应的Employeetypes。

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

我相信这个代码工作正常,但是当我有几个属性需要相同的行为时,它变得非常讨厌。

有没有其他方法来实现这种行为? generics或其他?

首先,你的问题的答案是否定的,C#不支持虚拟覆盖的任何forms的返回types协方差。

一些回答者和评论者曾经说过“这个问题没有协变性”。 这是不正确的; 原来的海报完全正确地提出了问题。

回想一下, 协变映射是保存其他关系的存在和方向的映射 。 例如,从typesT到typesIEnumerable<T>的映射是协变的,因为它保留了赋值兼容性关系。 如果Tiger与Animal分配兼容,则映射下的转换也被保留: IEnumerable<Tiger>IEnumerable<Animal>兼容。

这里的协变映射有点难以看清,但仍然存在。 这个问题本质上是这样的:这应该是合法的吗?

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

老虎是分配兼容动物。 现在从Ttypes映射到“public TM()”方法。 该映射是否保持兼容性 ? 也就是说, 如果老虎与动物兼容以达到任务的目的,那么为了实现虚拟覆盖,是否与public Animal M()兼容?

C#中的答案是“否”。 C#不支持这种协方差。

现在我们已经确定问题已经被问及使用正确的types代数术语,关于实际问题的几点思考。 显而易见的第一个问题是该属性甚至没有被声明为虚拟的,所以虚拟兼容性的问题是没有意义的。 显而易见的第二个问题是一个“get; set” 即使C#确实支持返回types协方差,属性也不能协变,因为具有setter的属性types不仅仅是它的返回types,它也是它的forms参数types 。 为了实现types安全,您需要对forms参数types进行变换 。 如果我们允许使用setter的属性返回types协方差,那么你会有:

 class B { public virtual Animal Animal{ get; set;} } class D : B { public override Tiger Animal { ... } } B b = new D(); b.Animal = new Giraffe(); 

嘿,我们刚刚把一只长颈鹿传给了正在等待一只老虎的二传手。 如果我们支持这个特性,我们不得不将它限制在返回types上(就像我们在通用接口上使用赋值兼容协变一样)。

第三个问题是CLR不支持这种方差; 如果我们想用语言来支持它(正如我认为托pipe的C ++所做的那样),那么我们将不得不采取一些合理的英雄措施来解决CLR中的签名匹配限制问题。

你可以通过仔细定义具有适当的返回types来映射其基类的“新”方法来自己做这些英雄的措施:

 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的实例,你会看到Tigertypes的属性。 如果你把它投给B,那么你会看到动物类属性。 无论哪种情况,您仍然通过受保护的成员获得虚拟行为。

总之,我们不打算做这个function,对不起。

您尝试实现的方式可能存在多个问题。

首先,正如有人已经注意到的那样,在你的例子中没有covarianace。 你可以在这里find关于协变和generics的简短描述, C#2.0中的新特性 – 方差,generics的协方差 。

其次,你似乎试图用generics来解决多态性应该解决的问题。 如果ExistingEmployeeNewEmployee都从一个基类Employeeinheritance,那么您的问题将得到解决:

 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 

在多态性和generics之间会有所不同的是,如果将variables返回到特定types,则在后面的情况下需要进行强制转换:

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

希望这可以帮助。

你可以编写一个员工界面来得到你想要的东西,我想。

 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> {} 

你发布的代码将不会编译,但我得到你想要做的基本想法。 总之答案是肯定的,那是唯一的办法。 如果你想要一个属性返回不同的types, 在扩展类中input不同的types那么你必须以你已经使用的方式使用generics。

如果您可以将新的或现有的员工对象的公共契约封装到一个接口中,则根本不需要使用generics。 相反,你可以返回接口,让多态性接pipe。

 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; } } 

你可以使用generics来实现这个看起来有点整洁的版本。

Covariant返回types被c#支持。 所以这不是一个解决scheme,但是,我的感觉是,从句法上来说,这个读起来很好。 它也取得了类似的结果。

我发现在创build基类需要执行某些操作的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,但对于内部使用,例如在unit testing中,我觉得它很有用。

是!! 喜欢这个。 有更多的锅炉板比你所希望的,但它确实工作。 技巧是用扩展方法完成的。 它在内部使用了一些令人讨厌的投射,但提出了一个协变界面。

另见: 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); } } } 

没有generics的一个想法,但它有其他缺点:

 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; }} } 

特别是对于这个代码,你可以投到基类并分配一个不需要的types的雇员。 所以你需要在基类的setter中添加检查。 或者移除我通常更喜欢的二传手。 这样做的一个方法是让制定者保护。
另一种方法是添加一个在派生类中重写的虚函数EmployeeType() ,并返回派生types。 然后你检查setter是否EmployeeType().IsInstanceOf(value) ,否则抛出exception。

国际海事组织模拟协变返回types是new标志less数几个很好的应用之一。 它返回与基类相同的东西,只是给函数合约增加了额外的保证。