Jochens Weblog

ESCde Developer Blog

  Home :: Kontakt :: RSS Feed
  12 Posts :: 0 Artikel :: 4 Kommentare :: 0 Trackbacks

Archiv

Post Kategorien

ESCde

ESCde Blogger

Freitag, 08. September 2006 #

Oftmals kommt man als Entwickler in die Verlegenheit, dass große Mengen von Objekten in kurzer Zeit erstellt werden müssen. Ein gutes Beispiel hierfür sind Algorithmen, welche während ihrer Laufzeit viele Objekte benötigen um Zustände und Zwischenergebnisse zu speichern.

Üblicherweise werden diese Objekte genau zu dem Zeitpunkt erstellt, zu dem sie auch benötigt werden, also mitten im Algorithmus. Das Erzeugen dieser Objekte kann allerdings recht teuer sein, wenn man bedenkt, dass die .NET Runtime hierzu erst passenden Speicher finden und reservieren muss.
Die Anzahl der verwendeten Objekte steht meist in Relation zu der Problemgröße, beispielsweise der Anzahl an untersuchten Dateien, der Größe eines Eingabearrays oder Ähnlichem. Hierdurch hat man meist schon vor dem Start des Algorithmus eine Ahnung, wie viele Objekte wohl benötigt werden. Dieses Wissen möchten wir uns hier nun zu Nutze machen.

Weiß man nämlich bereits vorab, wie viele Objekte benötigt werden, kann man diese bereits vor dem Ablauf des Algorithmus anlegen und die eigentliche Rechenzeit des Algorithmus somit verkürzen. Nehmen wir einmal an, Ihr Algorithmus würde während seiner Laufzeit viele Objekte des folgenden Typs benötigen:

class MemoryChunk { public int[] iData = new int[100]; }

Diese Klasse ist zwar recht unkompliziert, verlangt der .NET-Runtime jedoch einiges ab. Für eine Anwendung wäre es ein deutlicher Vorteil, wenn das erstellen dieser „Speicherklumpen“ bereits vorbereitet werden könnte.

Zu diesem Zweck erstellen wir eine weitere Klasse, den ObjectCreator:

class ObjectCreator<TargetType> where TargetType : class , new() { private TargetType[] mObjects; int mIndex = 0; public ObjectCreator() { } public void CreateObjects(int iCount) { if (iCount > 0) { mIndex = iCount - 1; mObjects = new TargetType[iCount]; for (int i = 0; i < iCount; i++) { TargetType obj = new TargetType(); mObjects[i] = obj; } } } public TargetType NewObject() { if (mIndex > 0) { TargetType obj = this.mObjects[mIndex]; mIndex--; return obj; } else return null; // oder: else return new TargetType(); } }

Wie Sie sehen handelt es sich hierbei um eine generische Klasse. TargetType gibt hierbei den Typ der zu erstellenden Objekte an, beispielsweise MemoryChunk. Durch Aufruf der Methode CreateObjects(AnzahlObj) können Sie ObjectCreator dazu veranlassen, AnzahlObj Objekte anzulegen. Durch Aufruf der Methode NewObject wird nun nach und nach je eines dieser Objekte zurückgegeben.

Die Anwendung der Klasse liegt nun auf der Hand: Wissen Sie beispielsweise, dass Ihr (gleich startender) Algorithmus 800 MemoryChunk-Objekte benötigt, erstellen Sie eine Instanz der Klasse ObjectCreator und rufen CreateObjects auf:

ObjectCreator<MemoryChunk> Creator = new ObjectCreator<MemoryChunk>(); Creator.CreateObjects(800);

Alle Codezeilen, in denen Sie innerhalb des Algorithmus ein MemoryChunk-Objekt erstellen, z.B. durch:

MemoryChunk mem = new MemoryChunk();

können Sie nun durch den folgenden Aufruf ersetzen:

MemoryChunk mem = Creator.NewObject();

Da die hier zurückgegebenen Objekte bereits existieren, werden Sie durch eine deutlich beschleunigte Laufzeit belohnt.

Nun kann man richtigerweise einwenden, dass die Objekte so oder so erstellt werden müssen, in der Summe sollte der Aufwand daher gleich bleiben. Dies ist so sicher richtig, allerdings gibt es viele Situationen, in denen man die „gesammelte“ Objekterstellung in Zeiten des Leerlaufs unterbringen kann. Nehmen Sie beispielsweise an, der Anwender fügt Einträge in einer Liste ein, welche anschließend von einem Algorithmus verarbeitet werden sollen. Während der Anwender den Mauszeiger zum Menüpunkt des Algorithmus bewegt, besteht genug Zeit, die nötigen Objekte vorab zu erstellen und die Wartezeit aufgrund der Abarbeitung für den Anwender zu minimieren.


Seit Version 6.0 hat sich Visual Basic deutlich weiterentwickelt. Hierbei fällt einem vor allem die nun (endlich) richtige Objektorientierung auf.

Inzwischen dürften wohl die meisten Visual Basic (2005) Programmierer mit den grundlegenden Mechanismen der Objektorientierung - Kapselung, Vererbung und Polymorphie - vertraut sein. Aber es gibt noch einige wenig bekannte Mechanismen, welche in Ausnahmesituationen recht hilfreich sein können.

Ein solcher Mechanismus ist "Shadowing". Stellen Sie sich vor, Sie haben eine Klasse A, welche eine Methode "Method()" enthällt. Der Aufruf von Method() erzeugt folgende Ausgabe:

"Eine Nachricht von Klasse A!"

Der passende Code würde so aussehen:

Class A Public Overridable Sub MyMethod() Console.WriteLine("Eine Nachricht von Klasse A!") End Sub End Class

Nun schreiben Sie eine weitere Klasse namens B, welche von A erbt. Sie überschreiben die Methode "Method()", so dass diese nun folgende Ausgabe vornimmt:

"Eine Nachricht von Klasse B!"

Diese würde wie folgt aussehen:

Class B : Inherits A Public Overrides Sub MyMethod() Console.WriteLine("Eine Nachricht von Klasse B!") End Sub End Class

Nun könten Sie folgendes tun:

Dim a As New A a.Method() Dim b As New B b.Method() a = b a.Method()

Die Ausgabe wäre dank Polymorphie folgende:

"Eine Nachricht von Klasse A!"
"Eine Nachricht von Klasse B!"
"Eine Nachricht von Klasse B!"

Was aber, wenn Sie möchten, dass sich ein Objekt sich wie der Typ verhällt, den die darauf zeigende Referenz hat? Hierzu können Sie sich des "Shadows" Schlüsselwortes bedienen. Schreiben Sie hierzu die Klasse B wie folgt:

Class B : Inherits A Public Shadows Sub MyMethod() Console.WriteLine("Eine Nachricht von Klasse B!") End Sub End Class

Die Ausgabe des Testcodes sieht nun wie folgt aus:

"Eine Nachricht von Klasse A!"
"Eine Nachricht von Klasse B!"
"Eine Nachricht von Klasse A!"

Shadows bewirkt also, das bei Methodenaufrufen durch eine "A"-Referenz, die Methode der A-Klasse aufgerufen wird, während bei einer "B"-Referenz die Methode von B genommen wird. Man könnte Shadows sozusagen auch als "Antipolymorphie" bezeichnen.

Nun, wann braucht man Shadows? Eigentlich sollte man es gar nicht benötigen, wenn Sie Shadows ernsthaft nutzen deutet dies meist auf einen Design-Fehler hin.

Allerdings gibt es einige wenige Situationen, in denen es sich durchaus als nützlich erweist. Stellen Sie sich vor, Sie entwickeln ein Control, welches andere Entwickler via Visual Studio Designer auf ein Form ziehen können. Anschließend kann der Entwickler via Properties Window die Eigenschaften des Controls bearbeiten.
Nun kann es passieren, dass dieses Control im Properties Window Eigenschaften anbietet, welche es von einer Basisklasse geerbt hat. Sie möchten diese dem Entwickler in Ihrem Control aber nicht anbieten. Durch Shadows können Sie solche Eigenschaften "verstecken", indem Sie beispielsweise das [Browsable(false)] Attribut anhängen.

An diesem Beispiel erkennt man aber schon, dass Shadows eher in Randsituationen benötigt wird und nicht in einem ordentlichen, objektorientierten Entwurf. Hier sollten Sie wirklich Abstand von der "dunklen Seite der Polymorphie" halten...