Rainer's profileCyrons BlogPhotosBlogListsMore Tools Help

Blog


    June 22

    StateManager mit eingeschränkter Kapazität (Undo/Redo)

    Um eine Möglichkeit zu bieten, den Speicherverbrauch zu reduzieren, habe ich den StateManager noch einmal erweitert. Dazu kann der Stack auf eine beliebige Anzahl an Elementen eingeschränkt werden (muß aber nicht). Wird die Kapazität überschritten, fällt das zuerst eingefügte (und damit älteste) Element hinten heraus.
    Was sich geändert hat:

    • Statt Stack<T> wird nun List<T> verwendet. Die neue Funktionalität lässt sich mit einem Stack nicht realisieren - jedenfalls nicht ohne speicher- und zeitaufwendige Umkopier-Aktionen.
    • Neue öffentliche Methode public void SetCapacity(uint capacity). Wird hier 0 als Parameter übergeben, ist die Liste unbegrenzt.
    • Neues öffentliches, readonly Property public uint Capacity
    • Das Property CloneStackCount wurde in CloneListCount umbenannt
    • Das Property UndoStackCount wurde in UndoListCount umbenannt
    • Die Methode ClearStacks() wurde in ClearLists() umbenannt.
    • Für Debugging-Zwecke sind zwei Properties List<ICloneable> CloneListMirror und List<ICloneable> UndoListMirror verfügbar
    • Ein Event CapacityExceeded wurde hinzugefügt, welches immer dann gefeuert wird, wenn ein altes Element aus der CloneListe herausfällt. Dieses Event benutzt den .net3.5 Delegate Action. Damit ist der StateManager nicht mehr kompatibel zu älteren .net2 Applikationen - aber irgendwann muß man sich ja mal vom Alten verabschieden. ;)
    namespace _04_StateManagerWithList
    {
       using System;
       using System.Collections.Generic;
       using System.Drawing;
    
       /// <summary>
       /// Bietet in einer abgeleiteten Klasse Funktionen für Undo und Redo.
       /// </summary>   
       public abstract class StateManager : IDisposable
       {
          #region Events
    
          /// <summary>
          /// Tritt ein wenn die Kapazität der Liste erreicht ist.
          /// </summary>
          public static event Action CapacityExceeded;
    
          #endregion
    
          #region Static Fields
    
          private static object syncLock = new object();
    
          #endregion
    
          #region Fields
    
          private List<ICloneable> cloneList = new List<ICloneable>();
          private bool disposed = false;
          private List<ICloneable> undoList = new List<ICloneable>();
    
          /// <summary>
          /// Hält die Information ob unmanaged Ressourcen verwendet werden oder nicht.
          /// </summary>
          private bool useImages;
    
          #endregion
    
          #region Public Properties
    
          /// <summary>
          /// Gibt die über die SetCapacity-Methode gesetzte Kapazität der CloneListe zurück.
          /// </summary>      
          public uint Capacity
          {
             get;
             private set;
          }
    
          /// <summary>
          /// Gibt den aktuellen Zählerstand der CloneListe zurück.
          /// </summary>      
          public int? CloneListCount
          {
             get
             {
                if (this.cloneList != null)
                   return this.cloneList.Count;
                else
                   return null;
             }
          }
    
          /// <summary>
          /// Gibt den aktuellen Zählerstand der UndoListe zurück.
          /// </summary>      
          public int? UndoListCount
          {
             get
             {
                if (this.undoList != null)
                   return this.undoList.Count;
                else
                   return null;
             }
          }
    
          #endregion
    #if DEBUG
          internal List<ICloneable> CloneListMirror
          {
             get
             {
                return cloneList;
             }
          }
    
          internal List<ICloneable> UndoListMirror
          {
             get
             {
                return undoList;
             }
          }
    #endif
    #region Protected Properties /// <summary> /// Setzt das aktuelle Objekt-Handle oder ruft dieses ab. /// </summary> protected ICloneable ObjectHandle { get; set; } #endregion #region Public Methods /// <summary> /// Löscht alle Inhalte der Listen, /// ohne die Listen-Instanzen selber zu zerstören. /// </summary> public void ClearLists() { this.cloneList.Clear(); this.undoList.Clear(); } /// <summary> /// Zerstört alle Instanzen im StateManager /// und gibt den von ihnen verwendeten Speicher frei. /// </summary> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Wiederholt die letzte Operation. /// Diese Methode ist threadsicher. /// </summary> /// <returns>Das Objekt in dem Zustand, /// in dem es sich vor dem letzten Undo befand.</returns> public ICloneable Redo() { lock (syncLock) { this.ObjectHandle = this.undoList[undoList.Count - 1]; this.undoList.RemoveAt(undoList.Count - 1); this.cloneList.Add(this.ObjectHandle); return this.ObjectHandle; } } /// <summary> /// Fügt eine Instanz in die CloneListe ein. /// Diese Methode ist threadsicher. /// </summary> /// <exception cref="NullReferenceException">Wirft eine NullReferenceException /// wenn versucht wird, ein leeres ObjectHandle an die Liste zu hängen.</exception> protected void Save() { lock (syncLock) { if (this.ObjectHandle != null) { AddObjectByCondition(); } else throw new NullReferenceException("ObjektHandle ist leer!"); } } /// <summary> /// Transportiert in einer abgeleiteten Klasse /// eine geklonte Instanz an den StateManager weiter. /// </summary> /// <param name="clone">Das geklonte Objekt</param> public abstract void SaveState(ICloneable clone); /// <summary> /// Setzt die Kapazität der CloneListe. /// Ein Wert von 0 deaktiviert die Kapazitätsbegrenzung. /// </summary> /// <param name="capacity">Die Kapazität</param> public void SetCapacity(uint capacity) { this.Capacity = capacity; } /// <summary> /// Macht eine Operation rückgängig. /// Diese Methode ist threadsicher. /// </summary> /// <returns>Das Objekt in dem Zustand, /// in dem es sich vor dem letzten Save befand.</returns> public ICloneable Undo() { lock (syncLock) { this.ObjectHandle = this.cloneList[cloneList.Count - 1]; this.cloneList.RemoveAt(cloneList.Count - 1); this.undoList.Add(this.ObjectHandle); this.ObjectHandle = this.cloneList[cloneList.Count - 1]; return this.ObjectHandle; } } #endregion #region Private Methods /// <summary> /// Schiebt ein neues Objekt an das Ende der Liste. /// </summary> private void AddDirect() { this.cloneList.Add(this.ObjectHandle); if (ObjectHandle.GetType() == typeof(Bitmap)) useImages = true; } /// <summary> /// Prüft, ob die Liste die mit SetCapacity angegebene Kapazität überschritten hat, /// und führt dem entsprechende Operationen aus. /// </summary> private void AddObjectByCondition() { if (Capacity == 0 || Capacity > 0 && cloneList.Count < Capacity) { AddDirect(); } else ClipAndAdd(); } /// <summary> /// Im Falle daß die Listenkapazität erreicht ist, /// wird das erste und damit älteste Element aus der Liste entfernt, /// und dann das neue Element am Ende angefügt. /// </summary> private void ClipAndAdd() { this.cloneList.RemoveAt(0); AddDirect(); OnCapacityExceeded(); } private void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { // prüfen ob Images auf der Liste liegen if (this.useImages) { // Images in der UndoListe zerstören while (this.UndoListCount > 0) { object obj = this.undoList[undoList.Count - 1]; this.undoList.RemoveAt(undoList.Count - 1); if (obj.GetType() == typeof(Bitmap)) { // Object in Image casten Image img = obj as Image; // Image zerstören img.Dispose(); } } // images in der CloneListe zerstören while (this.CloneListCount > 0) { object obj = this.cloneList[cloneList.Count - 1]; this.cloneList.RemoveAt(cloneList.Count - 1); if (obj.GetType() == typeof(Bitmap)) { Image img = obj as Image; img.Dispose(); } } } this.cloneList = null; this.undoList = null; this.ObjectHandle = null; } } this.disposed = true; } private void OnCapacityExceeded() { if (CapacityExceeded != null) CapacityExceeded(); } #endregion } }

    Demo

    using System; 
    class StringState : StateManager
       {
          #region Public Methods
    
          public override void SaveState(ICloneable clone)
          {
             ObjectHandle = clone.Clone() as ICloneable;
             Save();
          }
    
          #endregion
       }
    class Program
       {
          #region Fields
    
          StringState state;
    
          #endregion
    
          #region Constructors
    
          internal Program()
          {
             state = new StringState();
          }
    
          #endregion
    
          #region Private Methods
    
          private static void CapacityExceededEventHandler()
          {
             Console.WriteLine("Das älteste Element ist gerade hinten herausgefallen.");
          }
    
          private void DumpCloneStackContent()
          {
             foreach (var item in state.CloneListMirror)
             {
                Console.WriteLine(item.ToString());
             }
          }
    
          static void Main()
          {
             // amount ist größer als die Listenkapazität.
             uint amount = 12;
             Program that = new Program();
             StateManager.CapacityExceeded += new Action(CapacityExceededEventHandler);
             // Um den Unterschied zu sehen, folgende Zeile auskommentieren ->
             that.state.SetCapacity(10);
             // <-
             uint capacity = that.state.Capacity;
             if (capacity == 0)
                capacity = amount;
             for (int i = 0; i < amount; i++)
             {
                that.state.SaveState((i + 1).ToString());
             }
    
             Console.WriteLine("Inhalt der Cloneliste:");
             that.DumpCloneStackContent();
             Console.WriteLine("Taste drücken um fortzufahren.");
             Console.ReadKey();
             Console.WriteLine("Führe nun Undos aus...");
             for (int i = 0; i < capacity - 1; i++)
             {
                Console.WriteLine(that.state.Undo().ToString());
             }
    // Verhindert das selbsttätige Schließen des Konsolenfensters. Console.WriteLine("\nPress any key to terminate the program."); Console.ReadKey(); } #endregion }

    Output

    Das älteste Element ist gerade hinten herausgefallen.
    Das älteste Element ist gerade hinten herausgefallen.
    Inhalt der Cloneliste:
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Taste drücken um fortzufahren.
    Führe nun Undos aus...
    11
    10
    9
    8
    7
    6
    5
    4
    3

    Press any key to terminate the program.

    StateManager

    Technorati-Tags: ,,,

    June 16

    "Kann Datei xyz nicht löschen. Sie wird von einer anderen Person oder Programm benutzt"

    Zumindest so ähnlich lautet die Fehlermeldung, der man leider viel zu häufig begegnet. Die Lösung ist Unlocker, zu finden auf dieser Webseite. Ich benutze das Ding schon lange und kann es empfehlen.

    WARNUNG: Das Ding macht seinen Job. Dadurch können die Programme, welche das Laufwerk bzw. die Datei blockieren, instabil werden! Von einem allzu leichtfertigen Umgang mit Unlocker muß ich also abraten. Allerdings lässt sich so eine Instabilität i.d.R. durch ein Aus- und Wiedereinloggen oder einen Neustart des betroffenen Programms/Dienstes leicht wieder beheben. Das schöne bei Unlocker ist auch, daß er in einer Liste anzeigt, welche(s) Programm(e) für die Blockade verantwortlich ist/sind.

    StateManager mit Disposing

    Das ist die finale Version meines StateManagers - es sei denn ihr habt noch Wünsche. :)
    Erst der Code, dann folgen noch ein paar Worte.

    namespace StateManagerMitDisposing
    {
       using System;
       using System.Collections.Generic;
       using System.Drawing;
    
       /// <summary>
       /// Bietet in einer abgeleiteten Klasse Funktionen für Undo und Redo.
       /// </summary>   
       public abstract class StateManager : IDisposable
       {
          #region Static Fields
    
          private static object syncLock = new object();
    
          #endregion
    
          #region Fields
    
          private Stack<ICloneable> cloneStack = new Stack<ICloneable>();
          private Stack<ICloneable> undoStack = new Stack<ICloneable>();
    
          private bool disposed = false;
          /// <summary>
          /// Hält die Information ob unmanaged Ressourcen verwendet werden oder nicht.
          /// </summary>
          private bool useImages;
    
          #endregion
    
          #region Public Properties
    
          /// <summary>
          /// Gibt den aktuellen Zählerstand des CloneStacks zurück.
          /// </summary>      
          public int? CloneStackCount
          {
             get
             {
                if (this.cloneStack != null)
                   return this.cloneStack.Count;
                else
                   return null;
             }
          }
    
          /// <summary>
          /// Gibt den aktuellen Zählerstand des UndoStacks zurück.
          /// </summary>      
          public int? UndoStackCount
          {
             get
             {
                if (this.undoStack != null)
                   return this.undoStack.Count;
                else
                   return null;
             }
          }
    
          #endregion
    
          #region Protected Properties
    
          /// <summary>
          /// Setzt das aktuelle Objekt-Handle oder ruft dieses ab.
          /// </summary>
          protected ICloneable ObjectHandle { get; set; }
    
          #endregion
    
          #region Public Methods
    
          /// <summary>
          /// Löscht alle Inhalte der Stacks,
          /// ohne die Stack-Instanzen selber zu zerstören.
          /// </summary>
          public void ClearStacks()
          {
             this.cloneStack.Clear();
             this.undoStack.Clear();
          }
    
          /// <summary>
          /// Zerstört alle Instanzen im StateManager
          /// und gibt den von ihnen verwendeten Speicher frei.
          /// </summary>
          public void Dispose()
          {
             this.Dispose(true);
             GC.SuppressFinalize(this);
          }
    
          /// <summary>
          /// Wiederholt die letzte Operation.
          /// Diese Methode ist threadsicher.
          /// </summary>
          /// <returns>Das Objekt in dem Zustand,
          /// in dem es sich vor dem letzten Undo befand.</returns>
          public ICloneable Redo()
          {
             lock (syncLock)
             {
                this.ObjectHandle = this.undoStack.Pop();
                this.cloneStack.Push(this.ObjectHandle);
                return this.ObjectHandle;
             }
          }
    
          /// <summary>
          /// Fügt eine Instanz in den CloneStack ein.
          /// Diese Methode ist threadsicher.
          /// </summary>
          /// <exception cref="NullReferenceException">Wirft eine NullReferenceException
          /// wenn versucht wird, ein leeres ObjectHandle auf dem Stack zu speichern.</exception>
          protected void Save()
          {
             lock (syncLock)
             {
                if (this.ObjectHandle != null)
                {
                   this.cloneStack.Push(this.ObjectHandle);
                   if (ObjectHandle.GetType() == typeof(Bitmap))
                      useImages = true;
                }
                else
                   throw new NullReferenceException("ObjektHandle ist leer!");
             }
          }
    
          /// <summary>
          /// Transportiert in einer abgeleiteten Klasse
          /// eine geklonte Instanz an den StateManager weiter.
          /// </summary>
          /// <param name="clone">Das geklonte Objekt</param>
          public abstract void SaveState(ICloneable clone);
    
          /// <summary>
          /// Macht eine Operation rückgängig.
          /// Diese Methode ist threadsicher.
          /// </summary>
          /// <returns>Das Objekt in dem Zustand,
          /// in dem es sich vor dem letzten Save befand.</returns>
          public ICloneable Undo()
          {
             lock (syncLock)
             {
                this.ObjectHandle = this.cloneStack.Pop();
                this.undoStack.Push(this.ObjectHandle);
                this.ObjectHandle = this.cloneStack.Peek();
                return this.ObjectHandle;
             }
          }
    
          #endregion
    
          #region Private Methods
    
          private void Dispose(bool disposing)
          {
             if (!this.disposed)
             {
                if (disposing)
                {
                   // prüfen ob Images auf dem Stack liegen
                   if (this.useImages)
                   {
                      // Images im UndoStack zerstören
                      while (this.UndoStackCount > 0)
                      {
                         object obj = this.undoStack.Pop();
                         if (obj.GetType() == typeof(Bitmap))
                         {
                            // Object in Image casten
                            Image img = obj as Image;
                            // Image zerstören
                            img.Dispose();
                         }
                      }
                      // images im CloneStack zerstören
                      while (this.CloneStackCount > 0)
                      {
                         object obj = this.cloneStack.Pop();
                         if (obj.GetType() == typeof(Bitmap))
                         {
                            Image img = obj as Image;
                            img.Dispose();
                         }
                      }
                   }
                   this.cloneStack = null;
                   this.undoStack = null;
                   this.ObjectHandle = null;
                   /* Ohne Collect() kommt der GC vielleicht irgendwann mal vorbei,
                    * aber es soll ja schnell gehen. */
                   GC.Collect();
                }
             }
             this.disposed = true;
          }
    
          #endregion
       }
    }

    Laut CLRProfiler funktioniert das Disposing einwandfrei.

    Garbage Collection Statistics

    Ich habe eine Demoapplikation in mein Skydrive gestellt, mit der ihr euch selber einen kurzen Eindruck verschaffen könnt.

    StateManagerDemo

    June 09

    Undo/Redo, Nachtrag

    Kleine Erweiterung, große Wirkung.
    Die Stacks im StateManager sind private und so können die aktuellen "Füllstände" nicht abgefragt werden. Man kann zwar durch Exception handling schlimmeres verhindern, aber durch ein öffentliches Zählerproperty wird die ganze Sache angenehmer für den Entwickler.

    Hier nun der erweiterte StateManager

    namespace StateManagerCountExtension
    {
       using System;
       using System.Collections.Generic;
    
       public abstract class StateManager
       {
          #region private fields
          private static object syncLock = new object();
          private Stack<ICloneable> cloneStack = new Stack<ICloneable>();
          private Stack<ICloneable> undoStack = new Stack<ICloneable>();
          #endregion
    
          #region protected properties
          protected ICloneable ObjectHandle { get; set; }
          #endregion
    
          #region public properties
          public int CloneStackCount
          {
             get { return cloneStack.Count; }
          }      
    
          public int UndoStackCount
          {
             get { return undoStack.Count; }
          }
          #endregion
    
          #region Abstract Methods
          public abstract void SaveState(ICloneable clone);
          #endregion
    
          #region Public Methods
          protected void Save()
          {
             lock(syncLock)
             {
                this.cloneStack.Push(this.ObjectHandle);
             }
          }
    
          public ICloneable Redo()
          {
             lock(syncLock)
             {
                this.ObjectHandle = this.undoStack.Pop();
                this.cloneStack.Push(this.ObjectHandle);
                return this.ObjectHandle;
             }
          }
    
          public ICloneable Undo()
          {
             lock(syncLock)
             {
                this.ObjectHandle = this.cloneStack.Pop();
                this.undoStack.Push(this.ObjectHandle);
                this.ObjectHandle = this.cloneStack.Peek();
                return this.ObjectHandle;
             }
          }
          #endregion
       }
    }

    Und wieder ein kleines Demo (die Klassen Person und PersonState sind die gleichen wie im vorigen Artikel).

    namespace StateManagerCountExtension
    {
    using System;
    class Program { static void Main() { Person person = new Person(); PersonState personState = new PersonState(); Console.WriteLine("Anfängliche Stackzählerwerte:"); DumpStackCounters(personState, true); person.Firstname = "va"; person.Lastname = "na"; personState.SaveState(person); person.Firstname = "vb"; person.Lastname = "nb"; personState.SaveState(person); person.Firstname = "vc"; person.Lastname = "nc"; personState.SaveState(person); Console.WriteLine(personState.CloneStackCount.ToString() + " Personen wurden angelegt"); DumpStackCounters(personState, false); DumpActualPerson(person, true); for(int i = 0; i < 2; i++) { person = personState.Undo() as Person; } Console.WriteLine("Zweimal undo ausgeführt."); DumpStackCounters(personState, false); DumpActualPerson(person, true); Console.WriteLine("Zweimal redo ausgeführt."); for(int i = 0; i < 2; i++) { person = personState.Redo() as Person; } DumpStackCounters(personState, false); DumpActualPerson(person, true); Console.WriteLine("Versuche nun die Grenzen zu sprengen:"); try { person = personState.Redo() as Person; } catch(InvalidOperationException ex) { Console.WriteLine("Ein weiteres redo wurde versucht: " + ex.Message); } try { for(int i = 0; i < 3; i++) person = personState.Undo() as Person; } catch(InvalidOperationException ex) { Console.WriteLine("Dreimal undo wurde versucht: " + ex.Message); } // Dank des Counter-Properties ist jetzt aber Prävention möglich: Console.WriteLine("\r\nVersuche wieder 3 redos:"); if(personState.CloneStackCount > 3) for(int i = 0; i < 3; i++) person = personState.Redo() as Person; else Console.WriteLine("Operation nicht möglich!"); // Verhindert das selbsttätige Schließen des Konsolenfensters. Console.WriteLine("\nPress any key to terminate the program."); Console.ReadKey(); } private static void DumpActualPerson(Person person, bool lineFeed) { Console.WriteLine("Aktuelle Person ist: {0} {1}", person.Firstname, person.Lastname); if(lineFeed) Console.WriteLine("\r\n"); } private static void DumpStackCounters(PersonState personState, bool lineFeed) { Console.WriteLine("CloneStack-Zählerstand: {0} / UndoStack-Zählerstand: {1}", personState.CloneStackCount, personState.UndoStackCount); if(lineFeed) Console.WriteLine("\r\n"); } } } /* Output: Anfängliche Stackzählerwerte: CloneStack-Zählerstand: 0 / UndoStack-Zählerstand: 0 3 Personen wurden angelegt CloneStack-Zählerstand: 3 / UndoStack-Zählerstand: 0 Aktuelle Person ist: vc nc Zweimal undo ausgeführt. CloneStack-Zählerstand: 1 / UndoStack-Zählerstand: 2 Aktuelle Person ist: va na Zweimal redo ausgeführt. CloneStack-Zählerstand: 3 / UndoStack-Zählerstand: 0 Aktuelle Person ist: vc nc Versuche nun die Grenzen zu sprengen: Ein weiteres redo wurde versucht: Der Stapel ist leer. Dreimal undo wurde versucht: Der Stapel ist leer. Versuche wieder 3 redos: Operation nicht möglich! Press any key to terminate the program. */
    June 08

    Undo/Redo (multipel!) von Objektzuständen

    Das Command Pattern, so wie es im Buch Head first Design Patterns gezeigt wird, und so wie man es bei dofactory findet, speichert Befehle. Dazu muß für jeden Befehlstyp eine eigene Klasse definiert werden (z.B. LichtAnBefehl, LichtAusBefehl). Ich weiß nicht wie es euch geht, aber nachdem ich die besagten Samples studiert habe, bin ich zu dem Schluß gekommen, daß das Command Pattern in dieser Form wenig praxisnah ist.

    Zum Einen wird der Code durch die Definition von allen in Frage kommenden Befehlsklassen unheimlich aufgeblasen.
    Zum Anderen - und das ist viel schlimmer - kann das Command Pattern nur dort verwendet werden wo ein Undo durch eine inverse Funktion realisierbar ist. Zum Beispiel lautet in einem Befehl a += b. Im Undo würde dann eine inverse Funktion aufgerufen, die nichts weiter macht als a -= b.
    Wir alle kennen genügend Beispiele aus dem alltäglichen Leben wo das auf keinen Fall ausreicht. Wie ist das in Photoshop? Man malt einen Strich auf ein Bild und macht anschließend ein Undo.
    So etwas kann nicht berechnet werden!
    Dieses Problem wird gelöst, wenn man eben nicht Befehle speichert, sondern Zustände.

    StateManager Naked Demo


    Zuerst möchte ich kurz den kuriosen Namen dieses Projekts erklären, bevor falsche Verdächtigungen aufkommen:
    Der StateManager existiert mittlerweile als DLL in meinem Framework. Dieses Demo hier referenziert diese aber aus Demonstrationszwecken nicht, sondern lässt sich unter die Haube schauen, indem die entsprechenden Klassen direkt hier integriert sind. Darum naked!

    StateManager Klasse

    Sie ist abstrakt weil ein direkte Instantiierung nicht ratsam ist. Eine zwischengeschaltete ObjectState-Klasse muß die abstrakte Methode SaveState(ICloneable clone) überschreiben. Mit dem Parameter wird ein Objekt übergeben welches das ICloneable-Interface implementiert. Die Methoden Undo() und Redo() stammen jedoch direkt von der Basisklasse. Das macht den Code schlanker.

    ObjectState Klasse (hier PersonState und CarState)

    Diese Zwischenklasse liefert ein Objekt, welches das ICloneable Interface implementiert, an den StateManager. Jede ObjectState Klasse deklariert ihre eigene Instanz der StateManager-Klasse. Dadurch gibt es keine Konflikte.

    Objektklassen

    Alle Objekt-Typen können verwaltet werden. Es ist ganz egal ob es sich, wie hier um ein paar simple Stringproperties handelt, oder z.B. um Bitmaps.

    Output des Demos

    Startzustand : George Jetson
    Zweiter Zustand : Testvorname Testnachname
    Nach undo : George Jetson
    Nach redo : Testvorname Testnachname

    Jeder Objekt-Typ bekommt seinen eigenen StateManager ->
    Hier müßte Baxter Lomax stehen: Baxter Lomax
    Hier müßte Austin Martin, silver stehen: Austin Martin, silver

    Press any key to terminate the program.


    Und jetzt der Code:

    namespace StateManagerDemo
    {
       using System;
       using System.Collections.Generic;
    
       public abstract class StateManager
       {
          private static object syncLock = new object();
          private Stack<ICloneable> cloneStack = new Stack<ICloneable>();
          private Stack<ICloneable> undoStack = new Stack<ICloneable>();
          protected ICloneable ObjectHandle { get; set; }
    
          #region Abstract Methods
          public abstract void SaveState(ICloneable clone);
          #endregion Abstract Methods
    
          #region Public Methods
          protected void Save()
          {
             lock(syncLock)
             {
                this.cloneStack.Push(this.ObjectHandle);
             }
          }
    
          public ICloneable Redo()
          {
             lock(syncLock)
             {
                this.ObjectHandle = this.undoStack.Pop();
                this.cloneStack.Push(this.ObjectHandle);
                return this.ObjectHandle;
             }
          }
    
          public ICloneable Undo()
          {
             lock(syncLock)
             {
                this.ObjectHandle = this.cloneStack.Pop();
                this.undoStack.Push(this.ObjectHandle);
                this.ObjectHandle = this.cloneStack.Peek();
                return this.ObjectHandle;
             }
          }
          #endregion Public Methods
       }
    
       //======================================================================
    
       internal class Person : ICloneable
       {
          /* Auch private Member werden gesichert.
           * Das ist für zukünftige Erweiterungen von Interesse. */
          private string inofficial = "not official!";
    
          public string Firstname { get; set; }
          public string Lastname { get; set; }
          
    
          #region ICloneable Members
          public object Clone()
          {
             return this.MemberwiseClone();
          }
          #endregion
    
          public override string ToString()
          {
             return String.Format("{0} {1}", this.Firstname, this.Lastname);
          }
       }
    
       //======================================================================
    
       internal class PersonState : StateManager
       {
          #region StateManager Members
          public override void SaveState(ICloneable person)
          {
             this.ObjectHandle = person.Clone() as ICloneable;
             this.Save();
          }
          #endregion
       }
    
       //======================================================================
    
       internal class Car : ICloneable
       {
          public string TypeName { get; set; }
          public string Color { get; set; }
    
          #region ICloneable Members
          public object Clone()
          {
             return this.MemberwiseClone();
          }
          #endregion
    
          public override string ToString()
          {
             return String.Format("{0}, {1}", this.TypeName, this.Color);
          }
       }
    
       //======================================================================
    
       internal class CarState : StateManager
       {
          #region StateManager Members
          public override void SaveState(ICloneable car)
          {
             this.ObjectHandle = car.Clone() as ICloneable;
             this.Save();
          }
          #endregion
       }
    
       //======================================================================
    
       internal class Program
       {
          public static void Main()
          {
             Person p = new Person();
             PersonState personState = new PersonState();
    
             p.Firstname = "George";
             p.Lastname = "Jetson";
             personState.SaveState(p);
             Console.WriteLine("Startzustand    : " + p.ToString());
    
             p.Firstname = "Testvorname";
             p.Lastname = "Testnachname";
             personState.SaveState(p);
             Console.WriteLine("Zweiter Zustand : " + p.ToString());
    
             p = personState.Undo() as Person;
             Console.WriteLine("Nach undo       : " + p.ToString());
    
             p = personState.Redo() as Person;
             Console.WriteLine("Nach redo       : " + p.ToString());
    
             //======================================================================
    
             Console.WriteLine("\r\n");
    
             p.Firstname = "Baxter";
             p.Lastname = "Lomax";
             personState.SaveState(p);
             p.Firstname = "James";
             p.Lastname = "Bond";
             personState.SaveState(p);
    
             Car c = new Car();
             CarState carState = new CarState();
             c.TypeName = "VW";
             c.Color = "black";
             carState.SaveState(c);
             c.TypeName = "Austin Martin";
             c.Color = "silver";
             carState.SaveState(c);
             c.TypeName = "Maserati";
             c.Color = "gold";
             carState.SaveState(c);
    
             Console.WriteLine("Jeder Objekt-Typ bekommt seinen eigenen StateManager ->");
             p = personState.Undo() as Person;
             Console.WriteLine("Hier müßte Baxter Lomax stehen: " + p.ToString());
    
             c = carState.Undo() as Car;
             Console.WriteLine("Hier müßte Austin Martin, silver stehen: " + c.ToString());
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
       }
    }

    ClassDiagram