使用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来解决多态性应该解决的问题。 如果ExistingEmployee
和NewEmployee
都从一个基类Employee
inheritance,那么您的问题将得到解决:
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数几个很好的应用之一。 它返回与基类相同的东西,只是给函数合约增加了额外的保证。