Dictionary generisch einschränken
Frank Dzaebel, erstellt am:
5.10.2008, zuletzt geändert: 5.10.2008
Kategorie:Generica .NET-Version:3.5, [Download]
Bei einem generischen Dictionary mit einfachem TKey-Typ erscheint
beim Hinzufügen gleicher
"Keys" eine (gewünschte) Fehlermeldung. Will man, dass der Versuch eines Zufügens eines gleichen Key's zu
einer Exception führen soll, so kann man die Implementation in diesem Artikel (ganz unten) benutzen.
Nebenbei wird eine Methode aufgezeigt, wie man lokalisierte Fehlermeldungen des
Frameworks benutzen kann und Wertgleichheit generisch implementieren kann.
Weiterführende Artikel: BinarySearch des dichtesten
Elementes in C#
Als Hintergrund-Information zur Implementierung: Methoden
wie
ContainsKey
etc. gehen intern über GetHashCode-Methode
des GenericEqualityComparer. Dieser ist Referenz-orientiert und würde trotz gleicher
Eigenschaften-Werte: Ungleichheit zurückgeben. Das ist auch so definiert (Wertegleichheit, Referenzgleichheit). BTW: Beim Anwenden der
where T : class-Einschränkung
wird empfohlen, die Operatoren
== und !=
nicht für den Typparameter zu verwenden, da diese Operatoren nur die Referenzgleichheit
und nicht die Wertgleichheit prüfen.
Dictionary mit int-Typ als Schlüssel: ->gewünschter Fehler erscheint.
Dictionary<int, string> di = new Dictionary<int, string>();
di.Add(1, "Test1");
di.Add(1, "Test2"); // -> Gewünschter Fehler: ".. Schlüssel wurde bereits hinzugefügt".
Dictionary mit einfachem Person-Typ als Schlüssel: ->kein gewünschter Fehler erscheint.
Dictionary<Person, string> di = new Dictionary<Person, string>();
Person p1 = new Person(); p1.Vorname = "Vorname1"; p1.Alter = 42;
Person p2 = new Person(); p2.Vorname = "Vorname1"; p2.Alter = 42;
di.Add(p1, "Test1");
di.Add(p2, "Test2"); // -> Kein Fehler! Gewollt: Vergleich anhand der öffentlichen Eigenschafts-Werte.
...
class Person
{
public string Vorname { get; set; }
public int Alter { get; set; }
}
Dictionary mit Person-Typ als Schlüssel und Equals-Überschreibung:
-> gewünschter Fehler erscheint.
unschön: es wird nicht erzwungen, dass Person: Equals
und GetHashCode überschreiben muss
private void Form1_Load(object sender, EventArgs e)
{
Dictionary<Person, string> dic = new Dictionary<Person, string>();
FülleDaten(dic);
}
private static void FülleDaten(Dictionary<Person, string> dic)
{
Person p1 = new Person("Albert", new DateTime(1962, 6, 1));
Person p2 = new Person("Albert", new DateTime(1962, 6, 1));
dic.Add(p1, "Albert1");
dic.Add(p2, "Albert2"); // gewünschter Fehler erscheint
}
class Person // unschön: es wird nicht erzwungen, dass Person Equals und GetHashCode überschreiben muss
{
public Person(string vorname, DateTime geburt)
{
this.Vorname = vorname;
this.Geburtsdatum = geburt;
}
public string Vorname { get; set; }
public DateTime Geburtsdatum { get; set; }
public override bool Equals(object obj)
{
return this.ToString() == obj.ToString();
}
public override int GetHashCode()
{
return this.ToString().GetHashCode();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (PropertyDescriptor pd in
TypeDescriptor.GetProperties(typeof(Person)))
{
string val = pd.GetValue(this).ToString();
sb.AppendFormat("{0},", val);
}
return sb.ToString();
}
}
UniqueKeyDictionary mit Person-Typ als Schlüssel: -> gewünschter
Fehler erscheint.
Vorteil: es wird erzwungen, class Person:
IEquatable<TKey> implementiert.
private void Form1_Load(object sender, EventArgs e)
{
UniqueKeyDictionary<Person, string> dic =new UniqueKeyDictionary<Person,string>();
FülleDaten(dic);
}
private static void FülleDaten(UniqueKeyDictionary<Person,string> dic)
{
Person p1 = new Person("Albert",new DateTime(1962, 6, 1));
Person p2 = new Person("Albert",new DateTime(1962, 6, 1));
dic.Add(p1, "Albert1");
dic.Add(p2, "Albert2");// gewünschter Fehler erscheint
}
//<summary> Framework-Exceptions </summary>
static class Exceptions
{
/// <summary>Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt.
/// Vorteil: im Englischen UI gibt es eine analoge übersetzte Meldung.</summary>
public static readonly string Argument_AddingDuplicate = "Argument_AddingDuplicate";
}
/// <summary>Dictionary, das ein Einfügen gleicher Keys für
/// Klasseninstanzen (in der Equals-Semantik: Gleichheit aller
/// Eigenschaften-Werte) verhindert.</summary>
class UniqueKeyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
where TKey : IEquatable<TKey>
{
/// <summary>Fügt dem Dictionary einen 'TKey' mit
/// dem angegebenen 'value' zu. Bei einem bereits vorhandenen
/// Schlüssel wird eine Exception geworfen.</summary>
public new void Add(TKey key, TValue value)
{
if (this.Keys.Count > 0)
{
bool hasDuplicates = IsKeyDuplicate(key);
if (hasDuplicates)
{
ResourceManager rm = new ResourceManager("mscorlib",
typeof(Environment).Assembly);
string msg = rm.GetString("Argument_AddingDuplicate");
throw new ArgumentException(msg); // <<--- wie man Framework-Meldungen nutzen kann
}
}
base.Add(key, value);
}
public bool IsKeyDuplicate(TKey key)
{
foreach (TKey kyin Keys)
if (ky.Equals(key))
return true;
return false;
}
}
class Person : IEquatable<Person> // Vorteil: IEquatable wird durch den Constraint erzwungen.
{
public Person(string vorname, DateTime geburt)
{
this.Vorname = vorname;
this.Geburtsdatum = geburt;
}
public string Vorname { get; set; }
public DateTime Geburtsdatum { get; set; }
public override string ToString() // Vorteil: beim Debuggen sind Eigenschaften-Werte schnell ersichtlich
{
StringBuilder sb = new StringBuilder();
foreach (PropertyDescriptor pd in
TypeDescriptor.GetProperties(typeof(Person)))
{
string val = pd.GetValue(this).ToString();
sb.AppendFormat("{0},", val);
}
return sb.ToString();
}
public bool Equals(Person other) // keine direkte "Überschreibung" von Equals oder Hashcode nötig.
{
return this.ToString() == other.ToString();
}
}