简体   繁体   中英

Self-contained generic memento

Dearest fellow programmers,

I seem to lack some understanding as of how the referencing works in C#.

The case:
I tried to implement some sort of Memento proxy which would wrap an interface and store every parameter that we're provided to the method calls and store these into a list.

Whenever necessary we could call the RestoreState and the objects would "reset" to the original state.

The code:
Consumer and model object

class Program
{
    static void Main(string[] args)
    {
        IMemento memento = new Memento();
        PrestationInfo prestationInfo2 = new PrestationInfo { Advance = 2 };

        memento.Add(prestationInfo2);
        Console.WriteLine(prestationInfo2.Advance);   //Expect 2

        prestationInfo2.Advance = 1;
        Console.WriteLine(prestationInfo2.Advance);   //Expect 1

        memento.RestoreState();
        Console.WriteLine(prestationInfo2.Advance);   //Expect 2, but still 1

        Console.ReadKey();
    }
}

[Serializable]
public class PrestationInfo
{
    public int Advance { get; set; }
}

Memento

    public interface IMemento
{
    void Add(object pItem);
    void RestoreState();
}


public class Memento : IMemento
{
    public Memento()
    {
        MementoList = new Dictionary<long, object>();
        ReferenceList = new List<object>();
        ObjectIDGenerator = new ObjectIDGenerator();
    }


    private ObjectIDGenerator ObjectIDGenerator { get; set; }
    private Dictionary<long, object> MementoList { get; set; }
    private List<object> ReferenceList { get; set; } 


    public void Add(object pItem)
    {
        bool firstTime;
        long id = ObjectIDGenerator.GetId(pItem, out firstTime);

        if (firstTime)
        {
            var mementoObject = DeepCopy(pItem);
            MementoList.Add(id, mementoObject);

            ReferenceList.Add(pItem);
        }
    }

    public void RestoreState() 
    {
        for (int i = 0; i < ReferenceList.Count; i++)
        {
            object reference = ReferenceList[i];

            bool firstTime;
            long id = ObjectIDGenerator.GetId(reference, out firstTime);

            if (MementoList.ContainsKey(id))
            {
                object mementoObject = MementoList[id];

                reference = mementoObject;
                //reference = PropertyCopy<PrestationInfo>.CopyFrom(mementoObject as PrestationInfo);   //Property copy
                //Interlocked.Exchange(ref reference, mementoObject);   //Also tried this
            }
        }
    }


    private static TCopy DeepCopy<TCopy>(TCopy pObjectToCopy)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, pObjectToCopy);

            memoryStream.Position = 0;
            return (TCopy)binaryFormatter.Deserialize(memoryStream);
        }
    }
}

Extra info
My guess is, I'm doing/understand something wrong regarding the List.

I also tried the Interlocked.Exchange, playing around with "ref"'s, using WeakReference's and storing the object into a CareTaker object (and storing that CareTaker into the List), implement some copy Property thing...

And ... I just can't see it.

My expected result would be the PrestationInfo.Advance property containing the value 2. But it keeps

It looks like the problem is in your understanding of references in .NET

public void RestoreState() 
{
    for (int i = 0; i < ReferenceList.Count; i++)
    {
        object reference = ReferenceList[i];

        bool firstTime;
        long id = ObjectIDGenerator.GetId(reference, out firstTime);

        if (MementoList.ContainsKey(id))
        {
            object mementoObject = MementoList[id];

            reference = mementoObject;
            //reference = PropertyCopy<PrestationInfo>.CopyFrom(mementoObject as PrestationInfo);   //Property copy
            //Interlocked.Exchange(ref reference, mementoObject);   //Also tried this
        }
    }
}

The RestoreState method above does not return anything, and you're strictly operating on references, not their internal state. Inside your method object reference is a local reference. It is not the same as the external prestationInfo2 and your method simply makes reference point (refer) to the previously saved copy of the state of presentationInfo2 .

You could modify it something like this:

public object RestoreState() 
{
    for (int i = 0; i < ReferenceList.Count; i++)
    {
        object reference = ReferenceList[i];

        bool firstTime;
        long id = ObjectIDGenerator.GetId(reference, out firstTime);

        if (MementoList.ContainsKey(id))
        {
            object mementoObject = MementoList[id];

            reference = mementoObject;

            return reference;
        }
    }
    return null;
}

And then call it like this:

presentationInfo2 = memento.RestoreState();

If you want the memento to track objects and magically restore their state you will have to make the objects themselves aware of the memento which introduces coupling or use reflection to modify the tracked references internal state. Basically you don't deserialize the persisted state into a new object but use reflection to overwrite the previously stored internal state into the tracked object reference.

Be careful though to store references using WeakReference otherwise you will find yourself with a nice case of memory leak.

Memento.Add needs a ref parameter modifier to access the original reference type pointer.

https://msdn.microsoft.com/en-us/library/14akc2c7.aspx?f=255&MSPPError=-2147217396

Try this:

Change the Add method:

public long Add(object pItem)
{
    bool firstTime;
    long id = ObjectIDGenerator.GetId(pItem, out firstTime);

    if (firstTime)
    {
        var mementoObject = DeepCopy(pItem);
        MementoList.Add(id, mementoObject);

        ReferenceList.Add(pItem);
    }

    return id;  // i need my memento! LOL
}

You should also add this accessor method:

public object GetRestoredState(long id)
{
    return MementoList[id];  // you should put some range check here
}

Now that you have your id, you can fetch the restored state this way:

memento.RestoreState();
prestationInfo2 = memento.GetRestoredState(savedId); // <-- you got this when you called the Add()...
Console.WriteLine(prestationInfo2.Advance);   //Expect 2, but still 1

Follow ups: you can also make the IMemento into a IMemento<T> , and adjust your code accordingly

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM