是否可能对继承层次结构中的类的泛型成员强加类型约束?

时间:2012-04-10 20:26:38

标签: c# generics inheritance polymorphism

我正在用C#编写应用程序,并正在努力实现泛型。我有一个继承层次结构,由另一个继承层次结构(模型和视图模型)镜像,如下所示:

class A_Content { }

class B_Content : A_Content
{
    public string Bar;
}

class C_Content : A_Content
{
    public string Foo;
}

class A { public A_Content content; }
class B : A { }
class C : A { }

public class Test
{
    IList<A> A_Collection = new List<A>();

    public Test()
    {
        B b = new B();
        C c = new C();

        b.content = new B_Content();
        c.content = new C_Content();

        A_Collection.Add(b);
        A_Collection.Add(c);
    }
}

这很好用,但是没有对content强制执行任何类型约束,这使我每次想要使用它时都将它转换为正确的派生类。我想哄骗编译器强制执行B对象只有B_Content内容的约束。我的第一个切入点是:

class A_Content { }

class B_Content : A_Content
{
    public string Bar;
}

class C_Content : A_Content
{
    public string Foo;
}

class A { }
class B : A { B_Content content; }
class C : A { C_Content content; }

public class Test
{
    IList<A> A_Collection = new List<A>();

    public Test()
    {
        B b = new B();
        C c = new C();

        A_Collection.Add(b);
        A_Collection.Add(c);
    }
}

这很好用,但这意味着当我拥有content的集合时,我无法访问A的公共元素。我真正想做的是:

abstract class A_Content { }

class B_Content : A_Content
{
    public string Bar;
}

class C_Content : A_Content
{
    public string Foo;
}

abstract class A<T> { T content; }
class B : A<B_Content> { }
class C : A<C_Content> { }

public class Test {
    IList<A<A_Content>> A_Collection = new List<A<A_Content>>();

    public Test()
    {
        B b = new B();
        C c = new C();

        A_Collection.Add(b);
        A_Collection.Add(c);
    }
}

然而,这会产生一个错误,抱怨B不能隐式转换为A.我试过添加一个显式的强制转换无效。有没有办法表达我想要比第二个模型更优雅的约束?

4 个答案:

答案 0 :(得分:4)

你所追求的并不完全清楚。您是否尝试将A的每个实例都具有类型为Content的{​​{1}}属性,每个A_Content都有一个B属性,即{ {1}},依此类推?如果是这样,您就无法拥有Content / B_Content / etc。继承自B。 (无论如何,不​​是以非臭的方式)。 C的签名表示A属性应该能够获得(并且可能设置)A的任何有效值。您不能更改函数的返回类型或派生类中的属性或字段的类型。你可以使用泛型来基本上将属性的输入推迟到类的使用,但是这种语法会很难看,而且我不确定它会给你带来什么。

例如,你可以这样做:

Content

但这意味着两件事:

  • 您使用A_Contentpublic class A<TContent> where TContent : A_Content { public TContent Content { get; set; } } public class B<TContent> : A<TContent> where TContent : B_Content { // nothing here, as the property is already defined above in A } public class C<TContent> : A<TContent> where TContent : C_Content { // nothing here, as the property is already defined above in A } A的任何地方,您必须指定B的实际类型(CTContent,等等。)。哪个是痛苦
  • 绝对没有什么可以阻止你做A_Content这样的事情(实际上,在这种情况下基本上是B_Content,因为我们没有在课程中添加任何东西)。

简而言之,我认为你需要退后一步并提出新的设计。

顺便说一下

您的第二个示例未启动的原因(使用A<B_Content>)是因为您已告诉列表它需要包含B。由于List不满足,因此不起作用。这是典型的差异问题,它会混淆人们的很多。但请考虑这种情况(此代码将无法编译;它旨在说明潜在原因):

A<A_Content>

这显然会中断,因为B<B_Content> 实际上List<A<A_Content>> list = new List<A<A_Content>>(); list.Add(new B()); // this seems OK so far, right? A<A_Content> foo = list[0]; foo.content = new A_Content(): ,所以运行时不允许你将foo设置为等于B<B_Content>的实例(或从其继承的内容)content A_Content`或从其继承。

答案 1 :(得分:3)

您可以使用接口,以及接口成员的显式实现:

abstract class A_Content {}
class B_Content : A_Content {}
class C_Content : A_Content {}

interface IA
{
    A_Content content { get; }
}

abstract class A<T> : IA
    where T : A_Content
{
    T content;
    A_Content.content { get { return this.content; } }
}

class B : A<B_Content> {}
class C : A<C_Content> {}

然后,您可以设置List<IA>来保存BC个对象的同类集合。

事实上,使用C#4及更高版本,您可以使界面具有通用性和协变性;然后你可以隐式地实现接口(只要你使用属性而不是字段):

interface IA<out T>
{
    T content { get; }
}

abstract class A<T> : IA<T>
    where T : A_Content
{
    T content { get; set; }
}

class B : A<B_Content> {}
class C : A<C_Content> {}

现在,B仍然无法转换为A<A_Content>,但可以转换为IA<A_Content>,因此您可以使用List<IA<A_Content>>保持你的同类物品集合。

答案 2 :(得分:1)

好吧,编译器会产生错误,因为确实B无法转换为A<A_Content>。 这是因为A<A_Content> B的超类。 B类的父类是A<B_Content>

恐怕你需要坚持施法。这里需要它,因为你有A的列表。 如果你真的想避免投射(我不确定你为什么这么想),你可以试试动态调度。

您可以尝试创建List<dynamic>而不是List<A>。 但是,你至少需要C#4.0。

答案 3 :(得分:0)

希望我正确地承担你的意图,所以

拥有这样的集合

IList<A>表示您希望拥有一组A个具有不同实现方案的对象。

该属性是否为基类型的属性。这意味着基类型必须公开方法/属性=&gt;所以状态和行为原语,子类必须进行具体的实现。

这样的事情:

class A_Content {  public virtual string Bar {get;set;} }

class B_Content : A_Content
{
    public override string Bar {get;set;};
}

class C_Content : A_Content
{
    public override string Bar {get;set};
}

以及代码中的某个地方:

public Test()
{
      B b = new B();
      C c = new C();

      A_Collection.Add(b);
      A_Collection.Add(c);

      //so
      A_Collection[0].Bar // B::Bar 
      A_Collection[1].Bar //C::Bar 

}

而且你不需要投射到真实物体。简单的OOP方法。