在C#中检测可序列化数据类中的更改

时间:2014-06-01 07:41:35

标签: c# serialization poco

我一直在尝试检测C#中普通对象的变化。目标是为一堆数据对象提供容器类型类,这些数据对象可以在其中任何一个更改时作出反应。为了好玩,我想看看是否可以在容器类中完成所有工作,而不是诉诸于对象本身的属性和脏标志或事件。

我感到好奇的是,是否有一种智能,快速和有效的方法。我的尝试在下面,并且没有一个(为了一个开始,每个帧都需要调用“CheckStates'方法!”)我将其限制为仅允许每个实例一个实例类型,适合我的需要。

请注意,传入的对象可能如下所示:

[Serializable]
public class PlayerInfo
{
    public string name = string.Empty;
    public int score = 0;
}

然后是容器:

public class AppState
{
    private class StateData
    {
        public System.Object instance = null;
        public Byte[] currentState = new Byte[0];
        public Byte[] previousState = new Byte[0];
    }

    private Dictionary<Type, StateData> _allStates = new Dictionary<Type, StateData>();
    private BinaryFormatter _formatter = new BinaryFormatter();
    private MemoryStream _memoryStream = new MemoryStream();

    public T GetState<T>() where T : class, new()
    {
        T state = default(T);

        var stateType = typeof(T);
        StateData stateData;
        if(_allStates.TryGetValue(stateType, out stateData))
        {
            state = ReadData<T>(stateData);
        }
        else
        {
            var newState = CreateData<T>(out state);
            _allStates[stateType] = newState;
        }

        return state;
    }

    public void CheckStates()
    {
        foreach(var state in _allStates)
        {
            if(HasChanged(state.Value))
            {
                Console.WriteLine(state.Key.ToString() + " has changed");
                UpdateState(state.Value);
            }
        }
    }

    private StateData CreateData<T>(out T instance) where T : class, new()
    {
        instance = new T();

        var stateData = new StateData();
        stateData.instance = instance;
        _formatter.Serialize(_memoryStream, instance);
        var bytes = _memoryStream.ToArray();
        stateData.currentState = bytes;
        stateData.previousState = bytes;

        return stateData;
    }

    private T ReadData<T>(StateData data) where T : class, new()
    {
        return data.currentState as T;
    }

    private bool HasChanged(StateData data)
    {
        _memoryStream.Position = 0;
        _formatter.Serialize(_memoryStream, data.instance);
        var current = _memoryStream.ToArray();
        var previous = data.previousState;
        if(current.Length != previous.Length)
        {
            return true;
        }
        for(int i = 0; i < current.Length; ++i)
        {
            if(current[i] != previous[i])
            {
                return true;
            }
        }
        return false;
    }

    private void UpdateState(StateData data)
    {
        _memoryStream.Position = 0;
        _formatter.Serialize(_memoryStream, data.instance);
        data.previousState = _memoryStream.ToArray();
    }
}

我能想到的替代方案是:

  • 使用结构而不是可序列化的类(强制通过值传递意味着任何更改都必须通过容器上的“set”方法)
  • 拥有AppState&#39; GetState&#39;方法返回一个IDisposable包装器,在Dispose上可以触发检查该类型的更改(唯一的问题是没有什么可以阻止某人存储对该对象的引用并在没有容器知道的情况下修改它)

编辑:应该补充一点,它不需要是线程安全的

1 个答案:

答案 0 :(得分:0)

我不会将可序列化的类视为POCO,因为您正在设计类以便它们与您的更改检测机制一起使用。所以我不会简单地称它们为。

您的替代方案:

使用结构而不是可序列化的类

不要使用可变结构Why are mutable structs “evil”?。如果你的结构是不可变的,那么你也可以通过引用传递,即有一个类。

得到&#39; get&#39;方法返回IDisposable包装器

我不确定你指的是什么方法。

<强>代理

一种替代方法是允许后代代理对对setter的调用作出反应:

public class PlayerInfo
{
    public virtual string Name { get; set; }
    public virtual int Score { get; set; }
}

public class PlayerInfoDetection : PlayerInfo
{
    public int Revision { get; private set; }

    public override string Name
    {
        set
        {
            base.Name = value;
            Revision++;
        }
    }

    public override int Score
    {
        set
        {
            base.Score = value;
            Revision++;
        }
    }
}

private static void Example()
{
    PlayerInfo pi = new PlayerInfoDetection();
    Console.WriteLine(((PlayerInfoDetection)pi).Revision);
    pi.Name = "weston";
    Console.WriteLine(((PlayerInfoDetection)pi).Revision);
    pi.Score = 123;
    Console.WriteLine(((PlayerInfoDetection)pi).Revision);
}

这就是NHibernate&#34;观看&#34;从数据库中获取的对象,以及为什么每个对象属性必须在NHibernate中是虚拟的。

面向方面

使用像post sharp这样的产品可以实现同样的目的,您可以在必须更改修订时对类进行注释以告知它。

public class PlayerInfo
{
    public int Revision { get; private set; }
    public string Name { get; [IncreaseRevision] set; }
    public int Score { get; [IncreaseRevision] set; }
}

利用良好实施的哈希函数

当对象位于容器(如哈希集)中时,哈希函数不应更改其值。我们可以利用它来检测变化。

缺点请注意,任何哈希冲突都会产生不正确的结果。这包括重复。

[TestClass]
public class ChangeDetectUnitTest
{
    public class ChangeDetectList<T>
    {
        private readonly List<T> list = new List<T>();
        private readonly ISet<T> hashes = new HashSet<T>();

        public bool HasChanged(T t)
        {
            return !hashes.Contains(t);
        }

        public void Add(T t)
        {
            list.Add(t);
            hashes.Add(t);
        }

        public void Reset()
        {
            hashes.Clear();
            foreach (var t in list)
                hashes.Add(t);
        }
    }

    public class PlayerInfo
    {
        public string Name { get; set; }
        public int Score { get; set; }
        public override int GetHashCode()
        {
            //every field that you want to detect must feature in the hashcode
            return (Name ?? "").GetHashCode() * 31 + Score;
        }
        public override bool Equals(object obj)
        {
            return Equals(obj as PlayerInfo);
        }
        public bool Equals(PlayerInfo other)
        {
            if (other == null) return false;
            return Equals(other.Name, Name) && Score == Score;
        }
    }

    private ChangeDetectList<PlayerInfo> list;

    [TestInitialize]
    public void Setup()
    {
        list = new ChangeDetectList<PlayerInfo>();
    }

    [TestMethod]
    public void Can_add()
    {
        var p1 = new PlayerInfo();
        list.Add(p1);
        Assert.IsFalse(list.HasChanged(p1));
    }

    [TestMethod]
    public void Can_detect_change()
    {
        var p1 = new PlayerInfo();
        list.Add(p1);
        p1.Name = "weston";
        Assert.IsTrue(list.HasChanged(p1));
    }

    [TestMethod]
    public void Can_reset_change()
    {
        var p1 = new PlayerInfo();
        list.Add(p1);
        p1.Name = "weston";
        list.Reset();
        Assert.IsFalse(list.HasChanged(p1));
    }
}