0

在这个问题 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;
        }
    }
}
4

1 回答 1

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:回滚创建的ObjectTransaction。

于 2013-06-29T21:49:31.080 回答