【问题标题】:c# compare the data in two object modelsc#比较两个对象模型中的数据
【发布时间】:2011-01-06 15:35:04
【问题描述】:

我有一个对话框,它在生成时会填充对象模型中的数据。此时,数据被复制并存储在“备份”对象模型中。当用户完成更改并单击“确定”关闭对话框时,我需要一种快速比较备份对象模型和实时对象模型的方法 - 如果有任何更改,我可以为用户创建一个新的撤消状态。

如果可能的话,我不想为对象模型中的每个类都编写比较函数。

如果我序列化两个对象模型并且它们相同但存储在不同的内存位置,它们是否相等?是否存在一些简单的方法来比较两个序列化对象模型?

【问题讨论】:

  • 您能否提供更多有关您使用的技术的详细信息。您使用的是 WPF、WinForms 还是 Silverlight?使用 Entity Framework 或 WCF RIA 服务有一种非常简单的方法。
  • 嗨 Andrew - 我使用 .net 3.5 和 WPF 和 c# express 2008
  • 您使用的是实体框架、POCO 还是其他?
  • 那么,我猜你正在使用 POCO(Plain-old-CLR-objects)。我认为您的想法可能可行 - 只需序列化它们,然后可能散列该字符串,并比较两个对象模型之间的散列值。我不能保证这会可靠地工作,但我认为值得研究!跨度>
  • 好吧,首先序列化对象,然后计算序列化数据的哈希值。现在,请记住散列不会是唯一的 - 可能有两个不同的对象模型散列到相同的值,尽管我觉得这不太可能。

标签: c# serialization comparison


【解决方案1】:

我没有打扰哈希字符串,但直接二进制序列化就可以创造奇迹。当对话框打开时,序列化对象模型。

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

【讨论】:

  • 为什么不直接序列化数组并比较字符串/字节呢?如果它们相同,它们的序列化不会产生相同的东西吗?
【解决方案2】:

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

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

此 sn-p 由 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 命名空间中的各种类都使用它来确定相等。

我会说也检查一下,你就在路上了!

【讨论】:

  • 回答了“我如何比较两个对象是否相等”的问题,这是一种快速简便的方法来比较整个对象模型,而无需在每个类上编写比较代码。
  • 正如建议的那样,Reshaper 使生成这些比较变得非常容易。 jetbrains.com/help/resharper/2016.2/…
【解决方案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
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多