Dofactory.com
Dofactory.com

C# Memento Design Pattern

The Memento design pattern without violating encapsulation, captures and externalizes an object‘s internal state so that the object can be restored to this state later. 

C# code examples of the Memento design pattern is provided in 3 forms:

Frequency of use:
low
C# Design Patterns

UML class diagram

A visualization of the classes and objects participating in this pattern.

Participants

The classes and objects participating in this pattern include:

  • Memento  (Memento)
    • stores internal state of the Originator object. The memento may store as much or as little of the originator's internal state as necessary at its originator's discretion.
    • protect against access by objects of other than the originator. Mementos have effectively two interfaces. Caretaker sees a narrow interface to the Memento -- it can only pass the memento to the other objects. Originator, in contrast, sees a wide interface, one that lets it access all the data necessary to restore itself to its previous state. Ideally, only the originator that produces the memento would be permitted to access the memento's internal state.
  • Originator  (SalesProspect)
    • creates a memento containing a snapshot of its current internal state.
    • uses the memento to restore its internal state
  • Caretaker  (Caretaker)
    • is responsible for the memento's safekeeping
    • never operates on or examines the contents of a memento.

Structural code in C#

This structural code demonstrates the Memento pattern which temporary saves and restores another object's internal state.

using System;

namespace Memento.Structural
{
    /// <summary>
    /// Memento Design Pattern
    /// </summary>

    public class Program
    {
        public static void Main(string[] args)
        {
            Originator o = new Originator();
            o.State = "On";

            // Store internal state

            Caretaker c = new Caretaker();
            c.Memento = o.CreateMemento();

            // Continue changing originator

            o.State = "Off";

            // Restore saved state

            o.SetMemento(c.Memento);

            // Wait for user

            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Originator' class
    /// </summary>

    public class Originator
    {
        string state;

        public string State
        {
            get { return state; }
            set
            {
                state = value;
                Console.WriteLine("State = " + state);
            }
        }

        // Creates memento 

        public Memento CreateMemento()
        {
            return (new Memento(state));
        }

        // Restores original state

        public void SetMemento(Memento memento)
        {
            Console.WriteLine("Restoring state...");
            State = memento.State;
        }
    }

    /// <summary>
    /// The 'Memento' class
    /// </summary>

    public class Memento
    {
        string state;

        // Constructor

        public Memento(string state)
        {
            this.state = state;
        }

        public string State
        {
            get { return state; }
        }
    }

    /// <summary>
    /// The 'Caretaker' class
    /// </summary>

    public class Caretaker
    {
        Memento memento;

        public Memento Memento
        {
            set { memento = value; }
            get { return memento; }
        }
    }
}
Output
State = On
State = Off
Restoring state:
State = On

Real-world code in C#

This real-world code demonstrates the Memento pattern which temporarily saves and then restores the SalesProspect's internal state.

using System;

namespace Memento.RealWorld
{
    /// <summary>
    /// Memento Design Pattern
    /// </summary>

    public class Program
    {
        public static void Main(string[] args)
        {
            SalesProspect s = new SalesProspect();
            s.Name = "Noel van Halen";
            s.Phone = "(412) 256-0990";
            s.Budget = 25000.0;

            // Store internal state

            ProspectMemory m = new ProspectMemory();
            m.Memento = s.SaveMemento();

            // Continue changing originator

            s.Name = "Leo Welch";
            s.Phone = "(310) 209-7111";
            s.Budget = 1000000.0;

            // Restore saved state

            s.RestoreMemento(m.Memento);

            // Wait for user

            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Originator' class
    /// </summary>

    public class SalesProspect
    {
        string name;
        string phone;
        double budget;

        // Gets or sets name

        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                Console.WriteLine("Name:   " + name);
            }
        }

        // Gets or sets phone

        public string Phone
        {
            get { return phone; }
            set
            {
                phone = value;
                Console.WriteLine("Phone:  " + phone);
            }
        }

        // Gets or sets budget

        public double Budget
        {
            get { return budget; }
            set
            {
                budget = value;
                Console.WriteLine("Budget: " + budget);
            }
        }

        // Stores memento

        public Memento SaveMemento()
        {
            Console.WriteLine("\nSaving state --\n");
            return new Memento(name, phone, budget);
        }

        // Restores memento

        public void RestoreMemento(Memento memento)
        {
            Console.WriteLine("\nRestoring state --\n");
            Name = memento.Name;
            Phone = memento.Phone;
            Budget = memento.Budget;
        }
    }

    /// <summary>
    /// The 'Memento' class
    /// </summary>

    public class Memento
    {
        string name;
        string phone;
        double budget;

        // Constructor

        public Memento(string name, string phone, double budget)
        {
            this.name = name;
            this.phone = phone;
            this.budget = budget;
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public string Phone
        {
            get { return phone; }
            set { phone = value; }
        }

        public double Budget
        {
            get { return budget; }
            set { budget = value; }
        }
    }

    /// <summary>
    /// The 'Caretaker' class
    /// </summary>

    public class ProspectMemory
    {
        Memento memento;

        public Memento Memento
        {
            set { memento = value; }
            get { return memento; }
        }
    }
}
Output
Name:   Noel van Halen
Phone:  (412) 256-0990
Budget: 25000

Saving state --

Name:   Leo Welch
Phone:  (310) 209-7111
Budget: 1000000

Restoring state --

Name:   Noel van Halen
Phone:  (412) 256-0990
Budget: 25000

.NET Optimized code in C#

The .NET optimized code demonstrates the same code as above but uses more modern C# and .NET features.

Here is an elegant C# Memento solution.

namespace Memento.NetOptimized;

using System.Text.Json;
using static System.Console;

/// <summary>
/// Memento Design Pattern
/// </summary>
public class Program
{
    public static void Main()
    {
        // Init sales prospect through object initialization
        var s = new SalesProspect
        {
            Name = "Joel van Halen",
            Phone = "(412) 256-0990",
            Budget = 25000.0
        };

        // Store internal state
        var m = new ProspectMemory(s.SaveMemento());

        // Change originator
        s.Name = "Leo Welch";
        s.Phone = "(310) 209-7111";
        s.Budget = 1000000.0;

        // Restore saved state
        s.RestoreMemento(m.Memento);

        // Wait for user
        ReadKey();
    }
}

/// <summary>
/// The 'Originator' class
/// </summary>
public class SalesProspect
{
    private string name = null!;
    private string phone = null!;
    private double budget;

    // Gets or sets name
    public string Name
    {
        get => name; 
        set
        {
            name = value;
            WriteLine("Name:   " + name);
        }
    }

    // Gets or sets phone
    public string Phone
    {
        get => phone;
        set
        {
            phone = value;
            WriteLine("Phone:  " + phone);
        }
    }

    // Gets or sets budget
    public double Budget
    {
        get => budget;
        set
        {
            budget = value;
            WriteLine("Budget: " + budget);
        }
    }

    // Stores (serializes) memento
    public Memento SaveMemento()
    {
        WriteLine("\nSaving state --\n");

        var memento = new Memento();
        return memento.Serialize(this);
    }

    // Restores (deserializes) memento
    public void RestoreMemento(Memento memento)
    {
        WriteLine("\nRestoring state --\n");

        var s = (SalesProspect)memento.Deserialize();
        Name = s.Name;
        Phone = s.Phone;
        Budget = s.Budget;
    }
}

/// <summary>
/// The 'Memento' class
/// </summary>
public class Memento
{
    private string store = null!;

    public Memento Serialize(object o)
    {
        store = JsonSerializer.Serialize(o);
        return this;
    }

    public object Deserialize()
    {
        return JsonSerializer.Deserialize<SalesProspect>(store)!;
    }
}

/// <summary>
/// The 'Caretaker' class
/// </summary>
public record ProspectMemory (Memento Memento);

Output
Name:   Noel van Halen
Phone:  (412) 256-0990
Budget: 25000

Saving state --

Name:   Leo Welch
Phone:  (310) 209-7111
Budget: 1000000

Restoring state --

Name:   Noel van Halen
Phone:  (412) 256-0990
Budget: 25000



Last updated on Mar 17, 2024

Want to know more?


Learn how to build .NET applications in 33 days with design patterns, ultra clean architecture, and more.

Learn more about our Dofactory .NET developer package.


Guides


vsn 3.2