2009-08-26 7 views
2

Ich habe eine konkrete Klasse, die eine Sammlung einer anderen konkreten Klasse enthält. Ich mag beiden Klassen über Schnittstellen belichten, aber ich habe Probleme, herauszufinden, wie ich das Sammlung <ConcreteType> Mitglied als Sammlung <Schnittstelle> Mitglied aussetzen kann.Wie können Sie eine Sammlung <ConcreteType> als Sammlung zurückgeben <Interface>?

Ich bin derzeit .NET 2.0

Der Code unten führt zu einem Compiler-Fehler mit:

Cannot implicitly convert type 'System.Collections.ObjectModel.Collection<Nail>' to 'System.Collections.ObjectModel.Collection<INail>'

Der kommentierte Versuch zu werfen diesen Compiler-Fehler geben:

Cannot convert type 'System.Collections.ObjectModel.Collection<Nail>' to
'System.Collections.ObjectModel.Collection<INail>' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion.

Gibt es Gibt es eine Möglichkeit, die Sammlung konkreter Typen als eine Sammlung von Schnittstellen verfügbar zu machen, oder muss ich eine neue Sammlung in der Getter-Methode der Schnittstelle erstellen?

using System.Collections.ObjectModel; 

public interface IBucket 
{ 
    Collection<INail> Nails 
    { 
     get; 
    } 
} 

public interface INail 
{ 
} 

internal sealed class Nail : INail 
{ 
} 

internal sealed class Bucket : IBucket 
{ 
    private Collection<Nail> nails; 

    Collection<INail> IBucket.Nails 
    { 
     get 
     { 
      //return (nails as Collection<INail>); 
      return nails; 
     } 
    } 

    public Bucket() 
    { 
     this.nails = new Collection<Nail>(); 
    } 
} 
+0

Sie mögen die Serie von Artikeln hier lesen: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default .aspx –

Antwort

-1

C# unterstützt keine allgemeine Kovarianz von Auflistungen (sie wird nur für Arrays unterstützt). Ich verwende in solchen Fällen eine Adapterklasse. Es leitet nur alle Aufrufe an die tatsächliche Sammlung um und konvertiert die Werte in den erforderlichen Typ (es müssen nicht alle Listenwerte in die neue Sammlung kopiert werden). Verwendung sieht wie folgt aus:

Collection<INail> IBucket.Nails 
{ 
    get 
    { 
     return new ListAdapter<Nail, INail>(nails); 
    } 
} 

    // my implementation (it's incomplete) 
    public class ListAdapter<T_Src, T_Dst> : IList<T_Dst> 
{ 
    public ListAdapter(IList<T_Src> val) 
    { 
     _vals = val; 
    } 

    IList<T_Src> _vals; 

    protected static T_Src ConvertToSrc(T_Dst val) 
    { 
     return (T_Src)((object)val); 
    } 

    protected static T_Dst ConvertToDst(T_Src val) 
    { 
     return (T_Dst)((object)val); 
    } 

    public void Add(T_Dst item) 
    { 
     T_Src val = ConvertToSrc(item); 
     _vals.Add(val); 
    } 

    public void Clear() 
    { 
     _vals.Clear(); 
    } 

    public bool Contains(T_Dst item) 
    { 
     return _vals.Contains(ConvertToSrc(item)); 
    } 

    public void CopyTo(T_Dst[] array, int arrayIndex) 
    { 
     throw new NotImplementedException(); 
    } 

    public int Count 
    { 
     get { return _vals.Count; } 
    } 

    public bool IsReadOnly 
    { 
     get { return _vals.IsReadOnly; } 
    } 

    public bool Remove(T_Dst item) 
    { 
     return _vals.Remove(ConvertToSrc(item)); 
    } 

    public IEnumerator<T_Dst> GetEnumerator() 
    { 
     foreach (T_Src cur in _vals) 
      yield return ConvertToDst(cur); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    public override string ToString() 
    { 
     return string.Format("Count = {0}", _vals.Count); 
    } 

    public int IndexOf(T_Dst item) 
    { 
     return _vals.IndexOf(ConvertToSrc(item)); 
    } 

    public void Insert(int index, T_Dst item) 
    { 
     throw new NotImplementedException(); 
    } 

    public void RemoveAt(int index) 
    { 
     throw new NotImplementedException(); 
    } 

    public T_Dst this[int index] 
    { 
     get { return ConvertToDst(_vals[index]); } 
     set { _vals[index] = ConvertToSrc(value); } 
    } 
} 
+1

Diese Art von aussieht wie Overkill an diesem Punkt .... – RCIX

+0

Es ist Overkill, wenn Sie es nur einmal brauchen, aber diese Klasse macht das Leben leichter wenn Sie häufiger Kovarianz benötigen. – skevar7

+0

Cool, ich denke das ist genau was ich brauche! – jameswelle

6

C# 3.0-Generika sind invariant. Sie können das nicht tun, ohne ein neues Objekt zu erstellen. C# 4.0 führt eine sichere Kovarianz/Kontravarianz ein, die sowieso nichts an Lese/Schreib-Sammlungen (Ihrem Fall) ändert.

6

definieren Gerade Nägel als

Collection<INail> 
+2

Es ist eine Lösung, aber wenn Sie nur eine Auflistung von Nail-Objekt wünschen, ist das ein Problem, da jedes Objekt, das INail implementiert, in der Auflistung hinzugefügt werden konnte. –

+0

Ich würde damit bis C# 4 leben. Das hängt natürlich vom Original ab. – ChaosPandion

+2

Ich hätte erwähnen sollen, dass diese Klassen mit dem XmlSerializer serialisiert werden, weshalb die Sammlung als Collection und nicht Collection definiert werden muss. – jameswelle

1

Warum nicht einfach zurückgeben als Schnittstelle, haben nur alle öffentlichen Methoden in der Schnittstelle, auf diese Weise Sie dieses Problem nicht haben, und wenn Sie sich später entscheiden um eine andere Art von Nail-Klasse zurückzugeben, dann würde es gut funktionieren.

0

Welche Version von .Net verwenden Sie?

Wenn Sie .net 3.0+ verwenden, können Sie dies nur mit System.Linq erreichen.

Check out this question, die es für mich gelöst hat.

+0

Ich benutze .Net 2.0 – jameswelle

-1

könnten Sie die Cast-Erweiterung verwenden

nails.Cast<INail>() 

Ich kann es hier nicht testen ein umfassenderes Beispiel zu liefern, wie wir .NET 2.0 bei der Arbeit (meckern meckern) verwenden, aber ich hatte Ähnliche Frage here

+0

Dieses q ist .NET 2.0 spezifisch. – nawfal

0

Es gibt eine Lösung, die möglicherweise nicht ganz das ist, wonach Sie fragen, aber eine akzeptable Alternative sein könnte - verwenden Sie stattdessen Arrays.

internal sealed class Bucket : IBucket 
{ 
    private Nail[] nails; 

    INail[] IBucket.Nails 
    { 
     get { return this.nails; } 
    } 

    public Bucket() 
    { 
     this.nails = new Nail[100]; 
    } 
} 

(Wenn Sie so etwas wie dies am Ende tun, halten Sie dieses Framework Design Guidelines Anmerkung im Sinne: im Allgemeinen Arrays nicht als Eigenschaften ausgesetzt werden sollte, da sie vor ist den Anrufer und Kopieren zurück typischerweise kopiert werden eine teuere Operation innerhalb einer unschuldigen Eigenschaft get zu tun)

+0

Nooooooooo ... sollte in 99% der Fälle vermieden werden :) – nawfal

0

Verwendung dieses als der Körper Ihrer Eigenschaft Getter.

List<INail> tempNails = new List<INail>(); 
foreach (Nail nail in nails) 
{ 
    tempNails.Add(nail); 
} 
ReadOnlyCollection<INail> readOnlyTempNails = new ReadOnlyCollection<INail>(tempNails); 
return readOnlyTempNails; 

, dass ein bisschen etwas von einer hacky Lösung ist, aber es tut, was Sie wollen.

Bearbeitet, um eine ReadOnlyCollection zurückzugeben. Stellen Sie sicher, dass Sie Ihre Typen in IBucket und Bucket aktualisieren.

+0

Ich denke, das ist was ich tun muss, aber ich werde eine ReadOnlyCollection zurückgeben, da das Hinzufügen zur zurückgegebenen Sammlung das Original nicht beeinflusst. – jameswelle

+0

Dies ist überhaupt nicht hacky, ziemlich gesund und besser und einfacher als die meisten Antworten :) – nawfal

0

Sie einige Generika hinzufügen können. Passt besser, stärker gekoppelt.

public interface IBucket<T> where T : INail 
{ 
    Collection<T> Nails 
    { 
     get; 
    } 
} 

public interface INail 
{ 
} 

internal sealed class Nail : INail 
{ 
} 

internal sealed class Bucket : IBucket<Nail> 
{ 
    private Collection<Nail> nails; 

    Collection<Nail> IBucket<Nail>.Nails 
    { 
     get 
     { 
      return nails; //works 
     } 
    } 

    public Bucket() 
    { 
     this.nails = new Collection<Nail>(); 
    } 
} 

Auf diese Weise die Collection<Nail> Sie von Bucket Klasse zurückgeben kann nur immer Nail s halten. Irgendwelche anderen INail gehen nicht hinein. Dies kann oder kann nicht besser sein, je nachdem, was Sie wollen.


Nur wenn Sie Collection<INail> (die Schnittstelle Eigenschaft) möchten Sie zurückkommen von Bucket anderen INail s zu halten (als Nail s), dann können Sie die folgenden Ansatz versuchen. Aber es gibt ein Problem. Auf der einen Seite sagen, Sie ein private Collection<Nail> in Bucket Klasse verwenden möchten und kein Collection<INail>, weil Sie nicht wollen, versehentlich andere INail s aus Bucket Klasse hinein hinzuzufügen, aber auf der anderen Seite werden Sie andere INail s von außerhalb Bucket hinzufügen müssen Klasse. Dies ist nicht möglich auf der gleichen Instanz. Der Compiler verhindert, dass Sie versehentlich INail zu einem Collection<Nail> hinzufügen. Eine Möglichkeit besteht darin, eine andere Instanz Collection<INail> von Ihrer Bucket Klasse aus der vorhandenen Collection<Nail> zurückzugeben. Dies ist weniger effizient, aber könnte die Semantik sein, nach der Sie suchen. Beachten Sie, dass diese aus konzeptionell verschieden ist oben

internal sealed class Bucket : IBucket 
{ 
    private Collection<Nail> nails; 

    Collection<INail> IBucket<Nail>.Nails 
    { 
     get 
     { 
      List<INail> temp = new List<INail>(); 
      foreach (Nail nail in nails) 
       temp.Add(nail); 

      return new Collection<INail>(temp); 
     } 
    } 

    public Bucket() 
    { 
     this.nails = new Collection<Nail>(); 
    } 
} 
Verwandte Themen