利用泛型的c#协变返回类型

利用泛型的c#协变返回类型,c#,generics,covariance,C#,Generics,Covariance,下面的代码是实现协变返回类型的唯一方法吗 public abstract class BaseApplication<T> { public T Employee{ get; set; } } public class Application : BaseApplication<ExistingEmployee> {} public class NewApplication : BaseApplication<NewEmployee> {} 我相

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

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

public class Application : BaseApplication<ExistingEmployee> {}

public class NewApplication : BaseApplication<NewEmployee> {}
我相信这段代码工作得很好,但当我有几个属性需要相同的行为时,它会变得非常糟糕


是否有其他方法来实现此行为?泛型还是其他?

我认为,您可以针对员工界面进行编码,以获得您想要的

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> {} 
公共接口IEEmployee
{}
公共抽象类BaseApplication,其中T:IEmployee{
公共T雇员{get;set;}
} 
现有的公共类Employee:IEEmployee{}
公共类NewEmployee:IEEmployee{}
公共类应用程序:BaseApplication{}
公共类NewApplication:BaseApplication{}

您发布的代码不会编译,但我知道您想做什么。简言之,答案是肯定的,这是唯一的办法。如果您希望属性在扩展类中返回不同类型的,并以不同类型的,则必须以现有的方式使用泛型

如果可以将employee对象(新对象或现有对象)的公共契约封装到接口中,那么根本不需要使用泛型。相反,您可以返回接口,让多态性接管

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

您试图实现的目标可能存在多个问题

首先,正如有人已经注意到的,在您的示例中没有协方差。您可以在这里找到协方差和泛型的简短描述

其次,您似乎试图用泛型来解决多态性应该解决的问题。如果
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

希望这能有所帮助。

一个想法没有泛型,但它还有其他缺点:

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; }}
}
特别是使用这段代码,您可以强制转换到基类,并分配不需要的类型的employee。因此,您需要在基类的setter中添加检查。或者移除setter,我通常更喜欢它。一种方法是保护setter。
另一种方法是添加一个虚拟函数
EmployeeType()
,在派生类中重写该函数并返回派生类型。然后,如果
EmployeeType().IsInstanceOf(value)
,则检查setter,否则抛出异常


IMO模拟协变收益类型是
标记的少数几个良好应用之一。它返回与基类相同的内容,只是向函数契约添加了额外的保证。

更新:这个答案是在2010年编写的。二十年来,人们一直在为C#提出回归型协方差,但现在看来,它最终会实现;我相当惊讶。有关公告,请参见的底部;我相信细节会随之而来。以下答案中推测正在实施的功能的可能性的部分,应仅考虑未来的历史利益


首先,您的问题的答案是否定的,C#不支持虚拟覆盖上任何形式的返回类型协方差

一些回答者和评论者说“这个问题没有协方差”。这是错误的;原来的海报完全正确地提出了他们的问题

回想一下。例如,从类型
T
到类型
IEnumerable
的映射是协变的,因为它保留了赋值兼容性关系。如果Tiger的赋值与Animal兼容,则地图下的转换也会保留:
IEnumerable
的赋值与
IEnumerable
兼容

这里的协变映射有点难看,但它仍然存在。问题的实质是:这是否合法

class B
{
    public virtual Animal M() {...}
}
class D : B
{
    public override Tiger M() {...}
}
老虎和动物是相容的。现在创建一个从类型T到方法“public T M()”的映射。这种映射是否保持了兼容性?也就是说,如果Tiger与Animal兼容用于赋值,那么出于虚拟覆盖的目的,
public Tiger M()
是否与
public Animal M()
兼容

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

现在,我们已经确定问题是用正确的类型代数术语提出的,对实际问题还有一些想法。显而易见的第一个问题是,该财产甚至还没有被声明为虚拟财产,因此虚拟兼容性的问题是没有意义的。第二个明显的问题是,“get;set;”属性不可能是协变的,即使C#确实支持返回类型协变,因为带有setter的属性的类型不仅是其返回类型,也是其形式参数类型。您需要对形式参数类型进行逆变,以实现类型安全。如果我们允许使用setter的属性的返回类型协方差,那么您将有:

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

B b = new D();
b.Animal = new Giraffe();
嘿,我们刚刚把一只长颈鹿递给了一只正在等老虎的setter。如果我们支持此功能,我们将不得不将其限制为返回类型(就像我们在泛型接口上对赋值兼容性协方差所做的那样)

第三个问题是CLR不支持这种差异;如果我们想用语言支持它(我相信托管C++),那么我们就必须在CLR。< /P>中围绕签名匹配限制来做一些合理的英勇措施。 通过仔细定义“新”方法,您可以自己进行这些英勇的度量,这些方法具有适当的返回类型,这些返回类型会影响它们的基类类型:

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的实例,您将看到Tiger类型的属性。如果你把它投到B,那么你会看到动物类型的属性
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(); } }
}
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);
        }
    }
}
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();