2012-04-10 2 views
0

Ich schreibe eine Anwendung in C# und wrestle mit der Implementierung von Generika. Ich habe eine Vererbungshierarchie, die von einer anderen Vererbungshierarchie (Modelle und View-Modelle) wie so gespiegelt wird:Ist es möglich, einem generischen Member einer Klasse in einer Vererbungshierarchie Typbeschränkungen aufzuerlegen?

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

Dies funktioniert gut genug, aber erzwingt keine Art Einschränkungen content, die läßt ich es zu Gießen die richtige abgeleitete Klasse jedes Mal, wenn ich sie benutzen möchte. Ich möchte den Compiler dazu bringen, die Einschränkung zu erzwingen, dass B-Objekte nur B_Content Inhalt haben. Mein erster Schnitt an, dass war:

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

Das funktioniert gut, bedeutet aber, dass ich nicht die gemeinsamen Elemente von content zugreifen kann, wenn alles, was ich habe eine Sammlung von A s ist. Was ich wirklich tun möchte, ist so etwas wie:

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

Dies erzeugt jedoch ein Fehler, dass B nicht beschweren implizit in einen A umgewandelt werden kann, habe ich versucht, eine explizite Umwandlung ohne Erfolg hinzuzufügen. Gibt es eine Möglichkeit, die Einschränkungen, die ich suche, eleganter auszudrücken als das zweite Modell?

+0

Wenn ich die Frage richtig verstehe, was Sie hier suchen, ist eine Schnittstelle. Sie möchten eine Liste (), auf die jede Sammlung Zugriff hat, nein? –

+0

@BillCarey: Sie sollten nicht auf diese Weise gehen, weil Sie durch das Gießen auf einen bestimmten Typ ein Potential von OOP verlieren. Überprüfen Sie meine Antwort für eine andere Idee. – Tigran

Antwort

4

Es ist nicht ganz klar, was Sie wollen. Versuchen Sie, es so zu machen, dass jede Instanz von A eine Content Eigenschaft hat, deren Typ A_Content ist, jede B hat eine Content Eigenschaft, die eine B_Content ist, und so weiter? Wenn ja, können Sie das nicht tun und haben B/C/etc. erben von A. (nicht auf eine nicht-stinkende Weise, jedenfalls). Die Signatur von A besagt, dass die Content-Eigenschaft einen gültigen Wert von A_Content erhalten (und vermutlich auch setzen) kann. Sie können den Rückgabetyp einer Funktion oder den Typ einer Eigenschaft oder eines Felds in einer abgeleiteten Klasse nicht ändern. Sie könnte Generika verwenden, um die Eingabe der Eigenschaft auf die Verwendung der Klasse im Grunde zu verzögern, aber diese Syntax wird hässlich sein und ich bin mir nicht sicher, was es Ihnen bringt.

Zum Beispiel könnten Sie dies tun:

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

Das bedeutet aber zwei Dinge:

  • Sie überall verwenden A, B oder C Sie die tatsächliche Art der TContent angeben müssen (so A_Content, B_Content usw.). Was ist ein Schmerz
  • Es gibt absolut nichts, was Sie davon abhält, so etwas wie A<B_Content> (was in der Tat im Wesentlichen was B ist in diesem Fall, da wir nichts zu der Klasse hinzugefügt haben) zu stoppen.

Kurz gesagt, ich denke, Sie müssen zurückfallen und kotzen und ein neues Design entwickeln.

Durch die Art und Weise

Der Grund Ihres zweites Beispiel nicht (mit den List) fliegt, weil Sie die Liste gesagt haben, dass es A<A_Content> enthalten muss.Da B<B_Content> dies nicht erfüllt, wird es nicht funktionieren. Dies ist eine typische Varianz Frage und es verwirrt eine Los von Menschen. Aber betrachten Sie dieses Szenario (diesen Code nicht kompilieren, es ist beabsichtigt, demonstrative des zugrunde liegenden Grund zu sein):

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(): 

Dies würde offensichtlich brechen, da fooin Wirklichkeit ist ein B<B_Content>, so dass die Laufzeit wouldn Lassen Sie content gleich alles außer einer Instanz von B_Content (oder etwas, das davon erbt) , but the signature of the class means you should be able to assign anything that's A_Content_ oder erbt davon.

+0

+1 zum Lesen der Frage. zu lang für mich. –

3

Sie können eine Schnittstelle für diese, zusammen mit der expliziten Umsetzung des Mitglieds Schnittstelle (n):

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

Dann können Sie eine List<IA> machen eine homogene Sammlung von B und C Gegenstände zu halten.

In der Tat, mit C# 4 und höher, könnten Sie die Schnittstelle generisch und kovariant machen; dann können Sie die Schnittstelle implizit (solange man eher als ein Feld, das eine Eigenschaft verwenden) implementieren:

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

Nun B noch nicht zu A<A_Content> umgewandelt werden kann, aber es kann-IA<A_Content> umgewandelt werden, so dass Sie kann eine List<IA<A_Content>> verwenden, um Ihre homogene Sammlung von Objekten zu halten.

1

Nun, der Compiler erzeugt einen Fehler, weil in der Tat B nicht in A<A_Content> umgewandelt werden kann. Dies ist weil A<A_Content> ist nicht eine Oberklasse von B. Die Elternklasse der Klasse B ist A<B_Content>.

Ich fürchte, Sie müssen beim Gießen bleiben. Es wird hier benötigt, weil Sie eine Liste von A s haben. Wenn Sie das Casting wirklich vermeiden möchten (ich bin nicht sicher, warum Sie das möchten), können Sie es mit dynamischem Versand versuchen.

Sie können versuchen, List<dynamic> anstelle von List<A> zu erstellen. Sie werden jedoch mindestens C# 4.0 benötigen.

0

Hoffnung I undertsood Recht Absicht, so

wie diese

IList<A> eine Sammlung mit bedeutet, dass Sie eine Sammlung von A Objekten mit unterschiedlichen Implementierung Szenarien haben möchten.

Diese Eigenschaft, wenn die Eigenschaft eines Basistyps. Das bedeutet, dass der Basistyp Methoden/properties => so State- und Verhaltensprimitiven bereitstellen muss, die die untergeordneten Klassen eine konkrete Implementierung machen müssen.

Etwas wie folgt aus:

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

und irgendwo im Code:

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 

} 

Und Sie brauchen nicht zu realen Objekt zu werfen. Einfacher OOP-Ansatz.

Verwandte Themen