How is the Memento Pattern implemented in C#4?
Asked Answered
E

4

16

The Memento Pattern itself seems pretty straight forward. I'm considering implementing the same as the wikipedia example, but before I do are there any language features of C# that make it easier to implement or use?

Emboss answered 24/1, 2012 at 21:27 Comment(0)
S
17

One obvious feature would be generics, implementing an generic memento will allow you to use it for any object you want.

Many examples that you will see will use a string (including all those currently among the replies to this question) as state which is a problem since it's one of the few types in .NET which are immutable.

When dealing with mutable objects (like any reference type with a setter-property) you have to remember though that when you save the memento you need to create a deepcopy of the object. Otherwise whenever you change your original object you will change your memento.

You could do this by using a serializer like protobuf-net or json.net since they don't require you to mark your objects with serializable attribute like the normal .net serialization mechanism does.

Codeproject have few articles about generic memento implementations, but they tend to skip the deepcopy part:

Generic Memento Pattern for Undo-Redo in C#

Memento Design Pattern

Shrewish answered 24/1, 2012 at 21:51 Comment(1)
Can you add any further information about the deep copy part? A link to an example of this part of the implementation or something. Great answer so thanks...Myrmecology
S
3

I'm not aware of any already built-in way to support Memento pattern. I see a couple of implementations by using .NET Mock frameworks, where in practise a clone of the object is created and can be field with a data, but I consider it kind of overhead.

The use Memento patter on Undo/Redo usually, probably you too. In this case, it's better to have as less data on Undo/Redo stack as possible, so the custom undoable object is something that I would go for.

Hope this helps.

Salver answered 24/1, 2012 at 21:36 Comment(0)
H
2

There is one thing that will make this pattern marginally quicker to write in C# and that is that any state fields can be declared as public readonly so you don't need properties or 'get' methods to access them.

Here is a straight conversion with public readonly included.

class Originator 
{
    private string state;
    // The class could also contain additional data that is not part of the
    // state saved in the memento.

    public void Set(string state) 
    {
        Console.WriteLine("Originator: Setting state to " + state);
        this.state = state;
    }

    public Memento SaveToMemento() 
    {
        Console.WriteLine("Originator: Saving to Memento.");
        return new Memento(state);
    }

    public void RestoreFromMemento(Memento memento) 
    {
        state = memento.SavedState;
        Console.WriteLine("Originator: State after restoring from Memento: " + state);
    }

    public class Memento 
    {
        public readonly string SavedState;

        public Memento(string stateToSave)  
        {
            SavedState = stateToSave;
        }
    }
}

class Caretaker 
{
    static void Main(string[] args) 
    {
        List<Originator.Memento> savedStates = new List<Originator.Memento>();

        Originator originator = new Originator();
        originator.Set("State1");
        originator.Set("State2");
        savedStates.Add(originator.SaveToMemento());
        originator.Set("State3");
        // We can request multiple mementos, and choose which one to roll back to.
        savedStates.Add(originator.SaveToMemento());
        originator.Set("State4");

        originator.RestoreFromMemento(savedStates[1]);   
    }
}
Hypaethral answered 24/1, 2012 at 21:39 Comment(2)
Are you sure private constructor correct for Memento class? is it accessible from Originator?Ezraezri
You are right! Did I not compile this or something geez. Its fixed now thanks.Hypaethral
E
1

I've found one using Generics here:

#region Originator
public class Originator<T>
{
   #region Properties
   public T State { get; set; }
   #endregion
   #region Methods
   /// <summary>
   /// Creates a new memento to hold the current
   /// state
   /// </summary>
   /// <returns>The created memento</returns>
   public Memento<T> SaveMemento()
   {
      return (new Memento<T>(State));
   }
   /// <summary>
   /// Restores the state which is saved in the given memento
   /// </summary>
   /// <param name="memento">The given memento</param>
   public void RestoreMemento(Memento<T> memento)
   {
      State = memento.State;
   }
   #endregion
}
#endregion
#region Memento
public class Memento<T>
{
   #region Properties
   public T State { get; private set; }
   #endregion
   #region Ctor
   /// <summary>
   /// Construct a new memento object with the
   /// given state
   /// </summary>
   /// <param name="state">The given state</param>
   public Memento(T state)
   {
      State = state;
   }
   #endregion
}
#endregion
#region Caretaker
public class Caretaker<T>
{
   #region Properties
   public Memento<T> Memento { get; set; }
   #endregion
}
#endregion
#region Originator
public class Originator<T>
{
   #region Properties
   public T State { get; set; }
   #endregion
   #region Methods
   /// <summary>
   /// Creates a new memento to hold the current
   /// state
   /// </summary>
   /// <returns>The created memento</returns>
   public Memento<T> SaveMemento()
   {
      return (new Memento<T>(State));
   }
   /// <summary>
   /// Restores the state which is saved in the given memento
   /// </summary>
   /// <param name="memento">The given memento</param>
   public void RestoreMemento(Memento<T> memento)
   {
      State = memento.State;
   }
   #endregion
}
#endregion
#region Memento
public class Memento<T>
{
   #region Properties
   public T State { get; private set; }
   #endregion
   #region Ctor
   /// <summary>
   /// Construct a new memento object with the
   /// given state
   /// </summary>
   /// <param name="state">The given state</param>
   public Memento(T state)
   {
      State = state;
   }
   #endregion
}
#endregion
#region Caretaker
public class Caretaker<T>
{
   #region Properties
   public Memento<T> Memento { get; set; }
   #endregion
}
#endregion

Used like this:

   Originator<string> org = new Originator<string>();
   org.State = "Old State";
   // Store internal state in the caretaker object
   Caretaker<string> caretaker = new Caretaker<string>();
   caretaker.Memento = org.SaveMemento();
   Console.WriteLine("This is the old state: {0}", org.State);
   org.State = "New state";
   Console.WriteLine("This is the new state: {0}", org.State);
   // Restore saved state from the caretaker
   org.RestoreMemento(caretaker.Memento);
   Console.WriteLine("Old state was restored: {0}", org.State);
   // Wait for user
   Console.Read();

As @Simon Skov Boisen mentions this will only work for immutable data and requires a deep copy.

Emboss answered 24/1, 2012 at 21:48 Comment(1)
See my answer, you have to take into account deepcopying when dealing with reference types with setter properties.Shrewish

© 2022 - 2024 — McMap. All rights reserved.