C#中的单线程对象回滚

时间:2013-06-24 16:37:22

标签: c# .net transactions transactionscope

在这个问题上 Transactions for C# objects? 用户nicolas2008发布了能够回滚到更改对象的代码。我粘贴下面的代码。 我想问一下,代码是否可以安全使用,或者您是否看到了一些危险?它与Memento模式相比如何?

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;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

我的代码为支持回滚创建了真正深度复制对象 它不会引发任何事件。
它不会调用任何方法,构造函数或属性访问器。

如果对象树包含对以下内容的引用,则可能很危险:

  • 非托管资源(我认为它们无法正确复制),
  • 共享对象(因为它们也将被复制/回滚)。

您必须分析这些风险。 我建议你添加类型过滤器(在processRecursive方法的开头)或过滤其他字段元数据(在foreach循环中)以跳过执行深度复制这样的对象。例如:

    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);
            }

        //...
    }

在memento模式中,ObjectTransaction扮演Memento对象的角色:存储对象的状态 SaveToMemento:创建新的ObjectTransaction(thisObj) RestoreFromMemento:rollback created ObjectTransaction。