0

In this question Transactions for C# objects? user nicolas2008 posted the code which is able to do rollback to changed object. I paste the code below. I would like to ask is that code safe to use or do you see some dangers in that? Also how it compares to Memento pattern?

public sealed class ObjectTransaction : IDisposable
{
    bool m_isDisposed;

    Dictionary<object, object> sourceObjRefHolder;
    object m_backup;
    object m_original;

    public ObjectTransaction(object obj)
    {
        sourceObjRefHolder = new Dictionary<object, object>();
        m_backup = processRecursive(obj, sourceObjRefHolder, new CreateNewInstanceResolver());
        m_original = obj;
    }

    public void Dispose()
    {
        Rollback();
    }

    public void Rollback()
    {
        if (m_isDisposed)
            return;

        var processRefHolder = new Dictionary<object, object>();
        var targetObjRefHolder = sourceObjRefHolder.ToDictionary(x => x.Value, x => x.Key);
        var originalRefResolver = new DictionaryRefResolver(targetObjRefHolder);
        processRecursive(m_backup, processRefHolder, originalRefResolver);

        dispose();
    }

    public void Commit()
    {
        if (m_isDisposed)
            return;

        //do nothing
        dispose();
    }

    void dispose()
    {
        sourceObjRefHolder = null;
        m_backup = null;
        m_original = null;
        m_isDisposed = true;
    }

    object processRecursive(object objSource, Dictionary<object, object> processRefHolder, ITargetObjectResolver targetResolver)
    {
        if (objSource == null) return null;
        if (objSource.GetType() == typeof(string) || objSource.GetType().IsClass == false) return objSource;
        if (processRefHolder.ContainsKey(objSource)) return processRefHolder[objSource];

        Type type = objSource.GetType();
        object objTarget = targetResolver.Resolve(objSource);
        processRefHolder.Add(objSource, objTarget);

        if (type.IsArray)
        {
            Array objSourceArray = (Array)objSource;
            Array objTargetArray = (Array)objTarget;
            for (int i = 0; i < objSourceArray.Length; ++i)
            {
                object arrayItemTarget = processRecursive(objSourceArray.GetValue(i), processRefHolder, targetResolver);
                objTargetArray.SetValue(arrayItemTarget, i);
            }
        }
        else
        {
            IEnumerable<FieldInfo> fieldsInfo = FieldInfoEnumerable.Create(type);

            foreach (FieldInfo f in fieldsInfo)
            {
                if (f.FieldType == typeof(ObjectTransaction)) continue;

                object objSourceField = f.GetValue(objSource);
                object objTargetField = processRecursive(objSourceField, processRefHolder, targetResolver);

                f.SetValue(objTarget, objTargetField);
            }
        }

        return objTarget;
    }

    interface ITargetObjectResolver
    {
        object Resolve(object objSource);
    }

    class CreateNewInstanceResolver : ITargetObjectResolver
    {
        public object Resolve(object sourceObj)
        {
            object newObject = null;
            if (sourceObj.GetType().IsArray)
            {
                var length = ((Array)sourceObj).Length;
                newObject = Activator.CreateInstance(sourceObj.GetType(), length);
            }
            else
            {
                //no constructor calling, so no side effects during instantiation
                newObject = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(sourceObj.GetType());

                //newObject = Activator.CreateInstance(sourceObj.GetType());
            }
            return newObject;
        }
    }

    class DictionaryRefResolver : ITargetObjectResolver
    {
        readonly Dictionary<object, object> m_refHolder;

        public DictionaryRefResolver(Dictionary<object, object> refHolder)
        {
            m_refHolder = refHolder;
        }

        public object Resolve(object sourceObj)
        {
            if (!m_refHolder.ContainsKey(sourceObj))
                throw new Exception("Unknown object reference");

            return m_refHolder[sourceObj];
        }
    }
}

class FieldInfoEnumerable
{
    public static IEnumerable<FieldInfo> Create(Type type)
    {
        while (type != null)
        {
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (FieldInfo fi in fields)
            {
                yield return fi;
            }

            type = type.BaseType;
        }
    }
}
Community
  • 1
  • 1
user1121956
  • 1,843
  • 2
  • 20
  • 34

1 Answers1

1

My code creates realy deep copy of object for support rollback.
It is not fires any events.
It is not calls any methods, constructors or property accessors.

It can be dangerous if object tree contains references to:

  • unmanaged resources (they can not be copied correctly, I think),
  • shared objects (because they also will be copied/rolled back).

You must analyze this risks. I suggest you to add type filter (in the beginning of processRecursive method) or filter by other field metadata (in foreach loop) to skip perform deep copying such objects. For example:

    object processRecursive(object objSource, Dictionary<object, object> processRefHolder, ITargetObjectResolver targetResolver)
    {
        if (objSource == null) return null;
        //perform filter by type:
        if (typeof(System.Data.IDbConnection).IsAssignableFrom(objSource)) return objSource;            
        //...

            foreach (FieldInfo f in fieldsInfo)
            {
              if (f.FieldType == typeof(ObjectTransaction)) continue;

              object objSourceField = f.GetValue(objSource);
              object objTargetField = Attribute.IsDefined(f,typeof(MyAttributeForSkipTransaction)) //perform filter by field attribute
                                     ? objSourceField 
                                     : processRecursive(objSourceField, processRefHolder, targetResolver);

              f.SetValue(objTarget, objTargetField);
            }

        //...
    }

In memento pattern ObjectTransaction plays role of Memento object: stores state of object.
SaveToMemento: create new ObjectTransaction(thisObj).
RestoreFromMemento: rollback created ObjectTransaction.

nicolas2008
  • 945
  • 9
  • 11