in

AntMe! - Die Ameisensimulation

AntMe! 2.0 Blog

Gedanken zur neuen AntMe! Version.

Statische Generische Klassen und Bastis Tutorial

Vor ein paar Tagen hat Basti mir eine Ameisenklasse geschickt, die er von jemandem bekommen hat, die einen Bug in AntMe! demonstriert. Für solche Einsendungen sind wir immer dankbar, denn genau wie bei der Rechtschreibung gilt bei der Programmierung auch, dass man seine eigenen Fehler meistens nicht selbst findet. Nach Bastis E-Mail habe ich noch eine E-Mail von jemand anderem bekommen, der den selben Bug demonstriert hat. Es handelt sich hierbei um eine neue Version des Bug-Bugs durch die man einen Käfer mehrfach töten und damit sehr viele Punkte sammeln konnte. Das ist jetzt natürlich behoben. Wenn Käfer tot, dann Käfer tot. Nochmal draufhauen bringt nichts.

Die zweiten Person hat mir dann noch eine andere Ameisenklasse geschickt, durch die ich einen anderen lange gesuchten Bug gefunden habe. Vielen Dank Philipp! Dank dir bewegen sich Äpfel nun nicht mehr von alleine und in die falsche Richtung.

Wie auch immer. Mir war an Philipps Ameisenvolk eine Hilfklasse aufgefallen, die mir ein wenig seltsam erschien und in dem anderen Volk fand ich selbige Klasse auch wieder. Ich kam dann darauf: beide haben den KoordinatenSpeicher aus Bastis Globales Gedächtnis Tutorial verwendet. Das habe ich, weil ich was Ameisen angeht ein Fan von überhaupt-kein-Gedächtnis-und-schon-gar-nicht-global bin, zugegebenermaßen nie gelesen. Inzwischen habe ich das nachgeholt und das Tutorial ist (wie erwartet) richtig gut, aber eine kleine Sache darin ist sicher nicht im Sinne des Autors und damit kommen wir endlich zum Titel dieses Blog Eintrags.

Basti schreibt

Kommen wir also schon zu unserem ersten Schritt. Wir erstellen uns eine neue Klasse mit dem Namen „KoordinatenSpeicher“. Diese Klasse machen wir statisch und erstellen uns dort drin 3 Collections.

public static class KoordinatenSpeicher<K,V> {
    private static Dictionary<K, V> zuckerKoordinaten = new Dictionary<K, V>();
    private static Dictionary<K, V> obstKoordinaten = new Dictionary<K, V>();
    private static Collection<int> geloeschteObjekte = new Collection<int>();
    // Properties...
}

Jetzt Fragt ihr euch bestimmt was hat der Typ da gemacht. Ich erkläre es euch. Also wir ihr seht haben wir jetzt 3 Collections erstellt. Die erste Collection „zuckerKoordinaten“ ist dafür bestimmt alle Zucker Koordinaten abzuspeichern. Die 2te Collection „obstKoordinaten“ ist für die Obst Koordinaten zuständig. Dann seht ihr hier noch, dass ich hier generische Typen benutzt habe und zwar K und V. K steht in diesem Falle für den Typen des Keys und V für den Typen des Values. Als Key nehmen wir im weiteren Tutorial immer die ID des Obstes oder Zuckers und also Value immer das Obst oder den Zucker selbst, aber dazu später mehr. Als drittes haben wir noch die „geloeschteObjekte“, hier werden alle IDs der Obst oder Zuckerobjekte gespeichert, die schon gelöscht wurden, auch dazu später mehr. Das war dann schon erst einmal alles was wir für unseren Speicher benötigen um ihn zu benutzen.

Gestört hat mich daran der generische Typparameter <K,V> an der statischen Klasse KoordinatenSpeicher. Der Typparameter K für die Dictionary Instanzen ist im Tutorial immer int, bei der Collection Instanz steht aber nicht K sondern direkt int. Das ist meiner Meinung nach ein wenig inkonsequent. Der Zugriff auf den Speicher erfolgt unterschiedlich, je nachdem auf welche der internen Listen zugegriffen wird.

KoordinatenSpeicher<int, Zucker>.zuckerKoordinaten.Add(zucker.Id, zucker);
KoordinatenSpeicher<int, Obst>.obstKoordinaten.Add(obst.Id, obst);

Was nun passiert ist nämlich folgendes: der Compiler merkt sich, mit welchen Typparametern auf den KoordinatenSpeicher zugegriffen wird und erzeugt für jede Parameterkombination eine neue "Instanz" der statischen Klasse. Am Ende gibt es also nicht nur einen, sondern viele KoordinatenSpeicher, jeder mit eigenen Versionen der internen Listen. Freilich wird im Tutorial in jedem Speicher nur auf die Liste zugegriffen, die den passenden Namen hat. Aber folgendes ist trotzdem möglich:

KoordinatenSpeicher<int, Zucker>.obstKoordinaten.Add(zucker.Id, zucker);
KoordinatenSpeicher<int, Obst>.zuckerKoordinaten.Add(obst.Id, obst);

Das ganze ist nicht schlimm. Es werden einfach nur ein paar Listen erzeugt, die nie verwendet werden. Und es hat mich einmal zum Nachdenken angeregt und das kann nie schaden. Damit auch Anders Hejlsberg (der Erfinder von C#) wieder gut schlafen kann, schlage ich vor, die Klasse auf eine der folgenden Arten abzuändern. Ich favorisiere stark die zweite Methode, da dort einfach ersichtlicher ist was passiert:

public static class KoordinatenSpeicher<K,V> {
    private static Dictionary<K, V> koordinaten = new Dictionary<K, V>();
    private static Collection<K> geloeschteObjekte = new Collection<K>();
    // Properties...
}

public static class KoordinatenSpeicher {
    private static Dictionary<int, Zucker> zuckerKoordinaten = new Dictionary<int, Zucker>();
    private static Dictionary<int, Obst> obstKoordinaten = new Dictionary<int, Obst>();
    private static Collection<int> geloeschteObjekte = new Collection<int>();
    // Properties...
}

Was haben wir nun von der ganzen Sache? Zwei Bugs weniger und etwas dabei gelernt :-) Im Anhang zu diesem Blog Eintrag findet ihr ein kleines Demo Projekt, das die Sache anschaulich demonstriert.

There is no theory of evolution. Just a list of creatures Chuck Norris has allowed to live.

Veröffentlicht Nov 17 2007, 12:56 von wolfgang

Kommentare

 

basti sagte:

also man muss auch kein index mehr verwenden, sprich kein dictionary. Ist zwar ne nummer schneller als andere generische listen, aber man brauch es nicht mehr unbedingt.

November 19, 2007 1:50
© 2008 AntMe Limited | Impressum
© 2007 Microsoft Deutschland GmbH