Skip to content


Explicit Property Memento

Introduction

Some weeks ago I’ve introduced the Property Memento pattern as solution for temporarily saving the state of an entity’s property on creation of the Property Memento and restoring this initial state when the Property Memento disposes. I’ve shown that the using(){} syntax separates between the Property Memento initialization and the core logic which should be run inside the Memento with temporarily changed property values.

While I like the pattern and its usage, I see that the using syntax can be disturbing for developers who don’t know the internal implementation of the Property Memento. The implementation mixes using as common syntax with other semantics, which violates the uniformity principle of computer science.

A colleague suggested an implementation of the pattern, which makes the three steps in a Property Memento’s lifetime explicit:

  1. Memorize the current properties values.
  2. Invoke some actions.
  3. Restore the memorized values.

I’ve adopted his implementation to fit my needs of memorizing properties even for more than one entity.

Explicit Property Memento implementation

The implementation of the Explicit Property Memento projects the 3 steps of a Property Memento by the methods Memorize(), Invoke() and RestoreValues(). Thus the intention is revealed, which yields to a better understanding of the whole process. Instead of memorizing one property of just a single entity on the creation of the Property Memento, the ExplicitPropertyMemento has the ability to store several property values of more than one entity. This makes the implementation a bit heavier, but the usage very smart. First here’s the implementation part:

public class ExplicitPropertyMemento
{
    class MemorizedProperty
    {
        public string Name { get; set; }
        public object Value { get; set; }
    }

    private readonly IDictionary<object, List<MemorizedProperty>> _memorizedProperties
        = new Dictionary<object, List<MemorizedProperty>>();

    public static ExplicitPropertyMemento Create
    {
        get { return new ExplicitPropertyMemento(); }
    }

    public ExplicitPropertyMemento Memorize(
        object classInstance, params Expression<Func<object>>[] propertySelectors)
    {
        if(propertySelectors == null)
            return this;

        var properties = new List<MemorizedProperty>();
        foreach(var propertySelector in propertySelectors)
        {
            string propertyName = GetPropertyName(propertySelector);
            properties.Add(new MemorizedProperty
                {
                    Name = propertyName,
                    Value = GetPropertyValue(classInstance, propertyName)
                });
        }

        if(_memorizedProperties.ContainsKey(classInstance))
            _memorizedProperties[classInstance].AddRange(properties);
        else
            _memorizedProperties.Add(classInstance, properties);

        return this;
    }

    public ExplicitPropertyMemento Memorize<TProperty>(object classInstance,
        Expression<Func<object>> propertySelector, TProperty tempValue)
    {
        Memorize(classInstance, propertySelector);

        string propertyName = GetPropertyName(propertySelector);
        SetPropertyValue(classInstance, propertyName, tempValue);

        return this;
    }

    public ExplicitPropertyMemento Invoke(Action action)
    {
        try
        {
            action.Invoke();
        }
        catch
        {
            RestoreValues();
            throw;
        }

        return this;
    }

    public void RestoreValues()
    {
        foreach(var memorizedEntity in _memorizedProperties)
        {
            object classInstance = memorizedEntity.Key;
            foreach(var property in memorizedEntity.Value)
                SetPropertyValue(classInstance, property.Name, property.Value);
        }
    }

    private string GetPropertyName(Expression<Func<object>> propertySelector)
    {
        var body = propertySelector.Body;
        if (body.NodeType == ExpressionType.Convert)
            body = ((UnaryExpression)body).Operand;
        return ((MemberExpression)body).Member.Name;
    }

    private object GetPropertyValue(object classInstance, string propertyName)
    {
        return classInstance
            .GetType()
            .GetProperty(propertyName)
            .GetValue(classInstance, null);
    }

    private void SetPropertyValue(object classInstance, string propertyName, object value)
    {
        classInstance
            .GetType()
            .GetProperty(propertyName)
            .SetValue(classInstance, value, null);
    }
}

Usage example

The usage is explicitly projecting the Property Memento process by the methods Memorize(), Invoke() and RestoreValues() and it’s straightforward. In case of the introductory example it’s shown next:

private void SomeMethod(...)
{
    int oldValSumHeight = _valSum.Height;
    int newValSumHeight = 0;

    var newAnchor = AnchorStyles.Top | AnchorStyles.Left;
    ExplicitPropertyMemento.Create
        .Memorize(_valSum, () => _valSum.AutoSize, true)
        .Memorize(_valSum, () => _valSum.Anchor, newAnchor)
        .Memorize(_detailContent, () => _detailContent.Anchor, newAnchor)
        .Invoke(() =>
            {
                _valSum.SetValidationMessages(validationMessages);

                newValSumHeight = _valSum.Height;
                Height = Height - oldValSumHeight + newValSumHeight;
            })
        .RestoreValues();

    _valSum.Height = newValSumHeight;
}

Alternatively the ExplicitPropertyMemento implementation allows specifying more than one property as argument for the Memorize() method, if you don’t want to set the new property value directly by this method. Thus you could use some code like this:

private void SomeMethod(...)
{
    int oldValSumHeight = _valSum.Height;
    int newValSumHeight = 0;

    ExplicitPropertyMemento.Create
        .Memorize(_valSum, () => _valSum.AutoSize, () => _valSum.Anchor)
        .Memorize(_detailContent, () => _detailContent.Anchor)
        .Invoke(() =>
            {
                _valSum.AutoSize = true;
                _valSum.Anchor = AnchorStyles.Top | AnchorStyles.Left;
                _detailContent.Anchor = AnchorStyles.Top | AnchorStyles.Left;

                _valSum.SetValidationMessages(validationMessages);

                newValSumHeight = _valSum.Height;
                Height = Height - oldValSumHeight + newValSumHeight;
            })
        .RestoreValues();

    _valSum.Height = newValSumHeight;
}

Conclusion

Instead of the using syntax this article has shown an Explicit Property Memento implementation, which makes the whole Property Memento process explicit by defining methods for each step of the process. I still like the using syntax because it’s very dense, but in terms of intention revealing I also like the ExplicitPropertyMemento. What’s your opinion? At least feel free to choose the implementation you prefer :-)

kick it on DotNetKicks.com

Posted in Design patterns. Tagged with , , , , .

One Response

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Keith J. Farmer said

    Hmm… First off, I question why you’d need to specify the instance in two places. Specifically:

    Memorize(instance, () => instance.Property, …)

    You should be able to parse the lambda to verify that it’s of the form MemberAccess(Constant, PropertyInfo). Then the value of the constant expression would be the instance already.

    Memorize(() => instance.Property)
    For what it’s worth, I prefer the using syntax. I dislike that it’s an overload of the word “using”, but consider the possibility of modelling this using the existing Transaction API. For example:

    using (var t = new PropertyValueScope(() => instanceA.PropA, () => instanceB.PropB))
    {
    // possibly call t.Complete() to commit, or don’t and just roll back
    }

    Of course, you could just memorize all public properties implicitly:

    () => instance

    This would have the benefit of chasing down side-effecting property setters somewhat.

Some HTML is OK

(required)

(required, but never shared)

or, reply to this post via trackback.