C#比较两个对象模型中的数据

9
我有一个对话框,生成时会使用对象模型中的数据进行填充。此时,数据将被复制并存储在“备份”对象模型中。当用户完成更改并单击“确定”以关闭对话框时,我需要一种快速比较备份对象模型和实时对象模型的方法-如果有任何更改,我可以为用户创建新的撤销状态。
如果可能,我不想为每个对象模型中的类编写比较函数。
如果我将两个对象模型序列化并且它们在不同内存位置上但是完全一样,它们是否相等?是否存在一种简单的方法来比较两个序列化的对象模型?

1
你能提供更多关于你使用的技术的细节吗?你是在使用WPF、WinForms还是Silverlight?使用Entity Framework或WCF RIA Services可以很轻松地完成这项任务。 - Andrew Garrison
1
你使用的是Entity Framework、POCO还是其他什么东西? - Andrew Garrison
不,我之前没听说过那些。 - DrLazer
1
那么,我猜你正在使用POCO(普通的CLR对象)。我认为你的想法可能可行 - 只需将它们序列化,然后可能对该字符串进行哈希处理,并比较两个对象模型之间的哈希值。我不能保证这会可靠地工作,但我认为调查一下是值得的! - Andrew Garrison
1
首先对对象进行序列化,然后计算序列化数据的哈希值。请记住,哈希值不会是唯一的 - 可能会有两个不同的对象模型散列到相同的值,尽管我认为这种情况相当不可能。 - Andrew Garrison
显示剩余3条评论
3个回答

12

我没有使用哈希字符串,而是直接使用二进制序列化,效果非常好。当对话框打开时,序列化对象模型。

BinaryFormatter formatter = new BinaryFormatter();
m_backupStream = new MemoryStream();
formatter.Serialize(m_backupStream,m_objectModel);

如果用户使用可用控件添加到对象模型中(或不添加),当对话框关闭时,您可以将其与原始序列化进行比较,并使用新序列化来确定是否需要撤消状态。对我来说,这就是如何决定是否需要撤消状态。

BinaryFormatter formatter = new BinaryFormatter();
MemoryStream liveStream = new MemoryStream();
formatter.Serialize(liveStream,m_objectModel);
byte[] streamOneBytes = liveStream.ToArray();
byte[] streamTwoBytes = m_backupStream.ToArray();
if(!CompareArrays(streamOneBytes, streamTwoBytes))
    AddUndoState();

如果有人需要,这是比较两个数组的函数——我相信这不是最好的比较方法。

private bool CompareArrays(byte[] a, byte[] b)
{
    if (a.Length != b.Length)
       return false;

    for (int i = 0; i < a.Length;i++)
    {
       if (a[i] != b[i])
        return false;
    }
    return true;
}

为什么不直接对数组进行序列化,然后比较字符串/字节呢?如果它们完全相同,那么它们的序列化结果也应该是相同的吧? - Aelphaeis

7

我认为最好的方法是在您模型中的所有类上实现等式运算符(如果您要进行比较,这通常也是一个好主意)。

class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public ICollection<Chapter> Chapters { get; set; }

    public bool Equals(Book other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other.Title, Title) && Equals(other.Author, Author) && Equals(other.Chapters, Chapters);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (Book)) return false;
        return Equals((Book) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int result = (Title != null ? Title.GetHashCode() : 0);
            result = (result*397) ^ (Author != null ? Author.GetHashCode() : 0);
            result = (result*397) ^ (Chapters != null ? Chapters.GetHashCode() : 0);
            return result;
        }
    }
}

这段代码片段是由ReSharper自动生成的,但您可以将其用作基础。基本上,您需要使用自定义比较逻辑扩展未被覆盖的Equals方法。
例如,您可能希望使用Linq扩展中的SequenceEquals来检查章节集合是否按顺序相等。
现在,比较两本书就像简单地说一样:
Book book1 = new Book();
Book book2 = new Book();

book1.Title = "A book!";
book2.Title = "A book!";

bool equality = book1.Equals(book2); // returns true

book2.Title = "A different Title";
equality = book1.Equals(book2); // returns false

记住,还有一种实现相等性的方法:System.IEquatable,该接口被System.Collections命名空间中的各种类用于确定相等性。我建议你也查看一下这个,这样你就可以顺利地进行了!

我想知道如何比较两个对象是否相等,而且希望有一种快速简便的方法来比较整个对象模型,而不需要在每个类中编写比较代码。 - DrLazer
正如建议的那样,Reshaper使生成这些比较非常容易。https://www.jetbrains.com/help/resharper/2016.2/Code_Generation__Equality_Members.html - Andy Reed

3
我理解您的问题是如何在不事先了解类型(例如它们是否实现IEquatable或覆盖Equals)的情况下比较两个对象的值相等性(而不是引用相等性)。
为此,我建议两个选项:
A. 使用通用序列化类来序列化两个对象并比较它们的值。例如,我有一个名为XmlSerializer的类,它接受任何对象并将其公共属性序列化为XML文档。具有相同值和可能相同引用的两个对象在这个意义上具有相同的值。
B. 使用反射,比较两个对象的所有属性的值,例如:
bool Equal(object a, object b)
{
    // They're both null.
    if (a == null && b == null) return true;
    // One is null, so they can't be the same.
    if (a == null || b == null) return false;
    // How can they be the same if they're different types?
    if (a.GetType() != b.GetType()) return false; 
    var Props = a.GetType().GetProperties();
    foreach(var Prop in Props)
    {
        // See notes *
        var aPropValue = Prop.GetValue(a) ?? string.Empty;
        var bPropValue = Prop.GetValue(b) ?? string.Empty;
        if(aPropValue.ToString() != bPropValue.ToString())
            return false;
    }
    return true;
}

在这里,我们假设可以轻松比较属性,例如它们是否都实现了IConvertible接口,或者正确地重写了ToString方法。如果不是这种情况,我会检查它们是否实现了IConvertible接口,如果没有,就对属性递归调用Equal()方法。

这仅适用于比较公共属性。当然,您也可以检查私有和受保护的字段和属性,但是如果您对要比较的对象知之甚少,那么这样做可能会带来麻烦。


谢谢提供的信息,我确实考虑过使用反射,但是想要强调不需要编写大量比较代码。此外,它可能需要具有可扩展性,以便在需求发生变化并且需要添加新对象(或其他人添加)时使用。:S - DrLazer
谢谢。它帮助我比较了两个不同的对象。 - gouravm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接