Hinweis: Mit C# 4.0 wurde Ko- und Kontravarianz eingeführt. Siehe dazu die Anmerkung am Ende dieses Beitrags.
Beschreibung:
Die Klasse ReadOnlyBaseList <TBase, TDerived> ist ein Wrapper, den man immer dann benutzen kann, wenn man in einer Klasse eine Liste IList <TDerived> mit Elementen vom Unterklassentyp verwendet, die man aber nach außen als Liste IList<TBase> mit Elementen vom Oberklassentyp zur Verfügung stellen will oder muss.
Dabei muss TBase nicht wirklich eine Oberklasse sein, sondern kann auch ein Interface sein, das von TDerived implementiert wird.
Damit keine Elemente vom Typ TBase in die herausgereichte Liste - die ja hinter den Kulissen weiterhin von Typ IList <TDerived> ist - eingetragen werden, ist der Wrapper als ReadOnly-Collection implementiert.
Nach meinem Dafürhalten gibt es eine ganze Reihe von Fällen, in denen mal die ReadOnlyBaseList gut und sinnvoll verwenden kann. Einen konkreten Fall, in dem ich sie benötigt habe, findet ihr in [Lösung] Problem bei Entkoppelung durch Interface. Dort gibt es auch weitere Informationen zu der ReadOnlyBaseList-Klasse im Beitrag [Lösung] Problem bei Entkoppelung durch Interface.
using System;
using System.Collections.Generic;
using IUntypedEnumerator = System.Collections.IEnumerator;
using IUntypedEnumerable = System.Collections.IEnumerable;
using IUntypedList = System.Collections.IList;
using IUntypedCollection = System.Collections.ICollection;
//*****************************************************************************
public class ReadOnlyBaseList <TBase, TDerived> : IList <TBase>, IUntypedList
where TDerived : class, TBase
{
//--------------------------------------------------------------------------
private IList <TDerived> _listderived;
// Konstruktor
//==========================================================================
public ReadOnlyBaseList (IList <TDerived> listderived)
{
if (listderived == null) {
throw new ArgumentNullException ("listderived");
}
_listderived = listderived;
}
// Properties und Methoden von IList <TBase>
//==========================================================================
public TBase this [int iIndex] {
get { return _listderived [iIndex]; }
}
//==========================================================================
TBase IList<TBase>.this [int iIndex] {
get { return this [iIndex]; } // Unnötig, aber wird von Compiler erzwungen
set { throw new NotSupportedException (); }
}
//==========================================================================
public int IndexOf (TBase tbaseItem)
{
if (tbaseItem is TDerived || tbaseItem == null) {
return _listderived.IndexOf (tbaseItem as TDerived);
}
return -1;
}
//==========================================================================
void IList <TBase>.Insert (int iIndex, TBase tbaseItem)
{
throw new NotSupportedException ();
}
//==========================================================================
void IList <TBase>.RemoveAt (int iIndex)
{
throw new NotSupportedException ();
}
// Properties und Methoden von ICollection <TBase>
//==========================================================================
public int Count {
get { return _listderived.Count; }
}
//==========================================================================
public bool IsReadOnly {
get { return true; }
}
//==========================================================================
void ICollection <TBase>.Add (TBase tbaseItem)
{
throw new NotSupportedException ();
}
//==========================================================================
void ICollection <TBase>.Clear ()
{
throw new NotSupportedException ();
}
//==========================================================================
public bool Contains (TBase tbaseItem)
{
if (tbaseItem is TDerived || tbaseItem == null) {
return _listderived.Contains (tbaseItem as TDerived);
}
return false;
}
//==========================================================================
public void CopyTo (TBase [] atbase, int iIndex)
{
if (atbase == null) {
throw new ArgumentNullException ("atbase");
}
if (iIndex < 0) {
throw new ArgumentException ("iIndex");
}
if (atbase.Length - iIndex < _listderived.Count) {
throw new ArgumentException ("atbase/iIndex");
}
foreach (TDerived tderivedItem in _listderived) {
atbase [iIndex++] = tderivedItem;
}
}
//==========================================================================
bool ICollection <TBase>.Remove (TBase tbaseItem)
{
throw new NotSupportedException ();
}
// Properties und Methoden von IEnumerable <TBase>
//==========================================================================
public IEnumerator <TBase> GetEnumerator ()
{
return new ReadOnlyBaseListEnumerator (_listderived.GetEnumerator ());
}
// Properties und Methoden von IUntypedList
//==========================================================================
bool IUntypedList.IsFixedSize
{
get { return false; }
}
//==========================================================================
// ReadOnly wird oben für ICollection <TBase> nicht interface-explizit de-
// finiert, deshalb wird hier keine interface-explizit Definition benötigt.
//==========================================================================
Object IUntypedList.this [int iIndex] {
get { return this [iIndex]; } // Unnötig, aber wird von Compiler erzwungen
set { throw new NotSupportedException (); }
}
//==========================================================================
int IUntypedList.Add (Object objItem)
{
throw new NotSupportedException ();
}
//==========================================================================
void IUntypedList.Clear ()
{
throw new NotSupportedException ();
}
//==========================================================================
bool IUntypedList.Contains (Object objItem)
{
if (objItem is TDerived || objItem == null) {
return _listderived.Contains (objItem as TDerived);
}
return false;
}
//==========================================================================
int IUntypedList.IndexOf (Object objItem)
{
if (objItem is TDerived || objItem == null) {
return _listderived.IndexOf (objItem as TDerived);
}
return -1;
}
//==========================================================================
void IUntypedList.Insert (int iIndex, Object objItem)
{
throw new NotSupportedException ();
}
//==========================================================================
void IUntypedList.Remove (Object objItem)
{
throw new NotSupportedException ();
}
//==========================================================================
void IUntypedList.RemoveAt (int iIndex)
{
throw new NotSupportedException ();
}
// Properties und Methoden von IUntypedCollection
//==========================================================================
// Count wird oben für ICollection <TBase> nicht interface-explizit de-
// finiert, deshalb wird hier keine interface-explizit Definition benötigt.
//==========================================================================
bool IUntypedCollection.IsSynchronized
{
get { return false; }
}
//==========================================================================
Object IUntypedCollection.SyncRoot
{
get {
if (_listderived is IUntypedCollection) {
return ((IUntypedCollection)_listderived).SyncRoot;
}
return this; // Ich sehe im Gegensatz zu MS keinen Grund warum man
// nicht this als SyncRoot verwenden sollte.
}
}
//==========================================================================
void IUntypedCollection.CopyTo (Array a, int iIndex)
{
if (!(_listderived is IUntypedCollection)) {
// Die äußere Liste (ReadOnlyBaseList) unterstützt
// IUntypedCollection.CopyTo nur, wenn die innere Liste
// (_listderived) das tut.
// Anm. Die äußere Liste unterstützt immer ICollection <TBase>.CopyTo.
throw new NotSupportedException ();
}
((IUntypedCollection)_listderived).CopyTo (a, iIndex);
}
// Properties und Methoden von IUntypedEnumerable
//==========================================================================
IUntypedEnumerator IUntypedEnumerable.GetEnumerator ()
{
return ((IUntypedEnumerable)_listderived).GetEnumerator ();
}
//**************************************************************************
private class ReadOnlyBaseListEnumerator : IEnumerator <TBase>
{
//-----------------------------------------------------------------------
IEnumerator <TDerived> _ienumderived;
//=======================================================================
public ReadOnlyBaseListEnumerator (IEnumerator <TDerived> ienumderived)
{
_ienumderived = ienumderived;
}
//=======================================================================
public TBase Current
{
get { return _ienumderived.Current; }
}
//=======================================================================
Object IUntypedEnumerator.Current
{
get { return ((IUntypedEnumerator)_ienumderived).Current; }
}
//=======================================================================
public void Dispose ()
{
_ienumderived.Dispose ();
}
//=======================================================================
public bool MoveNext ()
{
return _ienumderived.MoveNext ();
}
//=======================================================================
public void Reset ()
{
_ienumderived.Reset ();
}
}
}
Anmerkungen zur Ko- und Kontravarianz in C# 4.0.
Aufgrund von Ko- und Kontravarianz ist es jetzt direkt in der Sprache möglich, Objekte vom Typ IList <TDerived> quasi so zu verwenden, als wäre IList <TDerived> eine Unterklasse von IList<TBase>. Damit käme man ab C# 4.0 im Prinzip ohne ReadOnlyBaseList <TBase, TDerived> aus.
Ich empfehle die Verwendung trotzdem weiterhin, wenn man explizit klar machen will, dass IList <TDerived> eben doch keine Unterklasse von IList<TBase> ist. Das zeigt sich ja genau daran, dass man einer Liste vom Typ IList <TDerived>, die man über eine Variable vom Typ IList <TBase> anspricht, trotzdem keine Objekte o vom Typ TBase oder eine Unterklasse davon hinzufügen darf, wenn diese nicht o is TDerived
erfüllen.
Selbst mit Ko- und Kontravarianz sind solche Operationen nicht erlaubt. Und auch Ko- und Kontravarianz kann in einem solchen Fall nichts besseres tun, als wie die ReadOnlyBaseList Exceptions zu werfen. Nur ist das bei der Ko- und Kontravarianz nicht so offensichtlich, wie bei der ReadOnlyBaseList, die schon im Namen auf den Umstand aufmerksam macht.
Einen Unterschied gibt es: Die ReadOnlyBaseList macht komplett dicht, wogegen bei Ko- und Kontravarianz das Hinzufügen von Objekten vom Typ TDerived erlaubt wäre. Dadurch das bei ReadOnlyBaseList schreibende Zugriffe grundsätzlich nicht erlaubt sind, sind jedoch etwaige Fehler auch leichter zu finden.
Schlagwörter: Interface, Entkoppelung, entkoppeln, Verallgemeinerung, verallgemeinern, Generalisierung, generalisieren, Abstraktion, abstrahieren, Wrapper, Design Pattern, Designpattern, Entwurfsmuster, ReadOnlyCollection, 1000 Worte