Posts mit dem Label Grundlagen werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Grundlagen werden angezeigt. Alle Posts anzeigen

Dienstag, 13. Januar 2009

DataBinding mit einer List<string>

Neulich stieß ich bei dem Versuch eine List<string> als Datenquelle für ein DataGridView zu verwenden auf ein interessantes Verhalten, das ich mir jedoch zunächst nicht erklären konnte.

Nehmen wir beispielsweise folgenden Code:

   1:  public FormMain()
   2:  {
   3:      this.InitializeComponent();
   4:   
   5:      List<string> tvStations = new List<string>();
   6:      tvStations.Add("Pro7");
   7:      tvStations.Add("RTL");
   8:      tvStations.Add("Sat1");
   9:      tvStations.Add("VOX");
  10:      tvStations.Add("Arte");
  11:      tvStations.Add("N24");
  12:      tvStations.Add("Phoenix");
  13:      tvStations.Add("RTL II");
  14:   
  15:      this.dgvTvStations.DataSource = tvStations;
  16:  }


Zu erwarten wäre, dass beim Start der Applikation unser DataGridView die in der Liste enthaltenen Fernsehsender anzeigt. Doch weit gefehlt, denn das Ergebnis sieht in Wirklichkeit so aus:

callingResult

Angezeigt werden uns also keineswegs die Fernsehsender sondern lediglich die Länge der Strings, die in der Liste enthalten sind.
Das liegt daran, dass die Datenquelle untersucht wird, wobei natürlich festgestellt wird, dass es sich um eine Liste von Strings handelt. Versucht man hier via Index auf einen bestimmten Wert zuzugreifen, wird man feststellen, dass die einzige verfügbare Eigenschaft dieses Wertes Length ist. Und genau diese wird dann vom DataGridView verwendet.

Um die Daten nun korrekt anzuzeigen muss ein Umweg in Kauf genommen werden.
Wir erstellen hierfür zunächst eine Klasse, die die Daten entgegennimmt, die wir anzeigen wollen. Im vorliegenden Fall könnte diese beispielsweise so aussehen:

   1:  public class TvStation
   2:  {
   3:      private string name;
   4:   
   5:      public string Name
   6:      {
   7:          get
   8:          {
   9:              return this.name;
  10:          }
  11:      }
  12:   
  13:      public TvStation(string name)
  14:      {
  15:          this.name = name;
  16:      }
  17:  }

 

Nun verwenden wir im Formular, das die Daten anzeigen soll, statt einer List<string> eine List<TvStation> und binden diese an den DataGridView.

   1:  public FormMain()
   2:  {
   3:      this.InitializeComponent();
   4:   
   5:      List<TvStation> tvStations = new List<TvStation>();
   6:      tvStations.Add(new TvStation("Pro7"));
   7:      tvStations.Add(new TvStation("RTL"));
   8:      tvStations.Add(new TvStation("Sat1"));
   9:      tvStations.Add(new TvStation("VOX"));
  10:      tvStations.Add(new TvStation("Arte"));
  11:      tvStations.Add(new TvStation("N24"));
  12:      tvStations.Add(new TvStation("Phoenix"));
  13:      tvStations.Add(new TvStation("RTL II"));
  14:      
  15:      this.dgvTvStations.DataSource = tvStations;
  16:  }

 

Wenn jetzt die Anwendung gestartet wird, zeigt der DataGridView tatsächlich die erwarteten Daten.

correctResult

Schade, dass in diesem Fall ein solcher Umweg nötig ist, aber so funktioniert DataBinding nun einfach mal.

Montag, 10. November 2008

Konsole in Konsolenanwendung verbergen

Gerade hatte ich mal wieder eine "wie Schuppen von den Augen"-Situation.

Zielsetzung war, eine Konsolenanwendung zu erstellen, bei der die Konsole nicht sichtbar sein sollte.
Zuerst habe ich an etwas gedacth wie Console.Hide(), doch eine solche Methode existiert nicht.
Dann habe ich Google bemüht und seltsame Verrenkungen gefunden, die einen DllImport benötigten. Auch nicht gerade die feine Art, wie ich fand.

Und dann schoss es mir in den Kopf: Einfach den Ausgabetyp auf Windows-Anwendung setzen und glücklich werden!
Schon erstaunlich, wie einfach es manchmal sein kann und wie weit man dennoch daneben liegt...

P.S.: Gerade habe ich noch gesehen, dass in den Kommentaren des obigen Blog-Eintrags die von mir verwendete Möglichkeit tatsächlich auch von jemandem erwähnt wurde.

Mittwoch, 3. September 2008

Hashtable und der loadFactor Parameter

Ich bin heute über den "ominösen" float loadFactor Parameter im Hashtable Constructor beim Fremdcode debuggen gestoßen. Wer sich nicht mit theoretischen Verständnis belasten will, nicht das "letzte" bischen Performance braucht und auch noch nie eine "System.InvalidOperationException: Hashtable insert failed. Load factor too high" Exception gesehen hat, dem sei gesagt, laß den loadFactor in Ruhe. Der ist Standardmäßig auf 0.72 eingestellt und völlig ausreichend normalerweise.

Wer jetzt noch dabei ist, der muss sich nun darauf gefasst machen das ich recht weit ausholen muß. Nach etwas Recherche in Wesner Moise's legendärem .NET undocumented blog, ging mir ein Licht auf, was dass denn nun genau ist. 
Theorie
Die Grundidee der Hashtable ist, simpel gesagt, mittels des Keys den man besitzt direkt zum Eintrag in einer Liste springen zu können. Kein suchen und langes vergleichen, man hat den Key, berechnet daraus eine Art "Offset" und hat damit den Speicherplatz lokalisiert wo der zugehörige Value abgelegt ist. Die Berechnung dieses "Offsets" übernimmt die Hashfunktion in unserer Hashtable. 
Z.b. wir haben eine Mitarbeiternummer für 50 Mitarbeiter, um jeden Eindeutig zu identifizieren würde es reichen aus den letzten 2 Nummern der Mitarbeiternummer einen eindeutigen Hashcode zu berechnen, ergo reicht uns ein vorinitialisiertes leeres Array von 100 Einträgen (nennen wir dieses Array Buckets) und je nach Hashcode kann man dann zum berechneten Index springen (egal ob 0 oder 99). Nun kommt aber Mitarbeiter 51, und dessen letzten 2 Ziffern aus der Mitarbeiternummer stimmen ganz zufällig mit der eines anderen überein. Ein insert schlägt fehl (Hashtable insert failed), unsere Hashfunktion ist nicht ausreichend und es gibt das, was man eine Collision in der Hashtable nennt. 
Um diese Collision  zu vermeiden kann man nun z.B. die Hashfunktion neu implementieren, nur haben wir dann das dilema, um so komplizierter und einzigartiger dieser Hashcode berechnet  wird, um so langsamer wird jeder Zugriff auf unsere Hashtable. Ein anderer Ansatz geht vom Load Factor der Hashtable aus. haben wir unsere Buckets aus dem obigen Beispiel mit der Größe 100 und 50 Einträge drin, dann beträgt der Load Factor 50/100 = 0.5. Das heißt unsere Buckets sind zu 50% gefüllt, die Wahrscheinlichkeit das eine Collision stattfindet ist 0.5. Um so niedriger wir diesen Faktor drücken können, um so unwahrscheinlicher ist eine Collision, aber auch um so mehr freien Speicherplatz verpulvern wir sprichwörtlich. 
Aber selbst wenn wir die Wahrscheinlichkeit drücken, sie bleibt bestehen, daher benötigt man eine Technik die im Falle der Collision eine Gegenstrategie fährt. Buckets machen aus unserem 1-dimensionalen Array ein 2-dimensionales Array. Ist der Index belegt, wird einfach in der 2. Dimension am nächsten freien Index unser Kollidierter Hash abgelegt. Praktisch auffüllen wie bei einem Eimer (Bucket). Die Nachteile sind offentsichtlich, das eingangs erwähnte "Vergleichen", dass man vermeiden wollte ist plötzlich wieder da, auch noch eine Collisiondetektions und Handlingsroutine.
Ergo man kann es also auf den Punkt bringen, mehr Speicherplatz für mehr Performance in der Hashtable. Vermeidet man Kollisionen, vermeidet man unnötige Operationszeiten. Sowohl beim schreiben und vor allem auch beim auslesen.
.NET Praxis
Nach dem theoretischen Ausflug zurück zu .NET. Ganz so simpel wie das theoretische Modell ist es nicht ganz in der Praxis.
Buckets sind in der .NET Hashtable ein Array von Arrays, um genau zu sein ein Bucket[], wobei jedes Bucket drei Werte hält: hash_coll (int), key (object) und val (object):
Füge ich in die obige Hashtable den key "a" ein, berechnet die Hashfunktion einen Wert dazu und ordnet diesem Wert einen Index in DIESEM(!) Bucket Array zu (andere Bucket Array Größe, andere Zuordnungsweise). Daher wird in dieser Hashtable (100, 1.0f), der key "a" immer an Position 49 im Bucket Array landen (.NET 3.5). Hätte ich nun einen key zur Hand, dessen Hashcode zufällig identisch wäre, würde eine Collision eintreten und das Collision Handling tritt ein. Dazu wird der Hashkey neu berechnet und als "Offset" zu der Kollision Index Position aufgerechnet. Es wird also nicht wie im klassischen Bucket in die 2. Dimension gegangen, sondern, flapsig gesagt, flach "weitergesprungen" (was uns den Compare aus der Theorie von oben erspart!).
Das wiederrum erklärt warum das Bucket Array hier deutlich größer ist als der vorgegebene Initialwert von 100. Und jetzt kommen wir damit auch endlich zum loadFactor und dessen Auswirkung. Ich habe den Wert von 1.0f übergeben als loadFactor. Dies wird mit dem Initialwert 0.72 multipliziert. D.h. würde ich 0.5f übergeben, wäre der interne loadFactor 0.36:
Die Berechnung der Bucket Anzahl ist jetzt eher nebensächlich, grob gesagt die Initialgröße dividiert durch den loadFactor und dann die nächste Primzahl (aus statistischen Gründen für maximale Effektivität). Wesner Moise läßt sich in seinem Blog detailierter darüber aus, wen es genauer interessiert (siehe Verlinkung oben).
Fazit
D.h. zusammenfassend:
um so kleiner der loadFactor...
-> um so größer das Bucket Array
-> um so mehr Speicherverbrauch
-> um so weniger Collisionen
-> um so weniger Operationen
-> um so höhere Performance in Abhängigkeit von der steigenden Anzahl der keys (was wirklich durchaus in einem signifikanten Rahmen liegt, ...probierts aus)

Donnerstag, 3. April 2008

Wie beende ich eine Applikation richtig?

Aus aktuellem Anlass hier mal etwas sehr grundlegendes, was einem in manchen Situationen aber absolut nicht einfallen will - eventuell, weil es zu einfach ist? ;)

Problemstellung:
Wir haben innerhalb eines FormLoad-EventHandlers eine Verzweigung, die entweder den EventHandler rekursiv aufruft, oder die Applikation beendet.

Lösungsansatz:
Für den Fall, dass die Applikation beendet werden soll, wird einfach ein Application.Exit() aufgerufen. Klingt ja so weit schön einfach. Wird aber ein folgendes Konstrukt zusammengestöpselt, erleben wir eine Überraschung.

   1:  Form1_Load(object sender, EventArgs e)
   2:  {
   3:      int retry = 0;
   4:   
   5:      try
   6:      {
   7:          string s = IrgendeinAufruf();
   8:      }
   9:      catch(Exception)
  10:      {
  11:          retry++;
  12:   
  13:          if(retry < 4) // 3 Versuche
  14:          {
  15:              Form1_Load(sender, e);
  16:          }
  17:          else
  18:          {
  19:              Application.Exit();
  20:          }
  21:      }
  22:   
  23:      MacheIrgendwasMitEinemString(s);
  24:  }

Hier werden wir eine NullReferenceException erhalten, denn das Application.Exit() wirkt nicht sofort, was dazu führt, dass die Methode 'MacheIrgendwasMitEinemString' auch noch ausgeführt wird, wobei der String natürlich nicht initialisiert wurde.

Die Lösung des Problems ist ein kleines, süßes, nützliches, gewöhnliches 'return', wodurch der korrekt funktionierende Code dann so aussieht:

   1:  Form1_Load(object sender, EventArgs e)
   2:  {
   3:      int retry = 0;
   4:   
   5:      try
   6:      {
   7:          string s = IrgendeinAufruf();
   8:      }
   9:      catch(Exception)
  10:      {
  11:          retry++;
  12:   
  13:          if(retry < 4) // 3 Versuche
  14:          {
  15:              Form1_Load(sender, e);
  16:          }
  17:          else
  18:          {
  19:              Application.Exit();
  20:              return;
  21:          }
  22:      }
  23:   
  24:      MacheIrgendwasMitEinemString(s);
  25:  }

Schlußfolgerung:
Würdigt das RETURN, hegt es, pflegt es und vor allem: nutzt es da, wo es nötig ist! ;)