【问题标题】:Circular reference error when serializing objects with custom Equals and GetHashCode methods使用自定义 Equals 和 GetHashCode 方法序列化对象时出现循环引用错误
【发布时间】:2012-01-26 10:52:02
【问题描述】:

当我的 asmx Web 服务的 WebMethod 返回我的对象​​时,我得到 在序列化 T 类型的对象时检测到循环引用错误

如果我从 T 类中删除 Equals 和 GetHashCode,问题就会消失。 我没有任何循环引用,所以看起来序列化通过比较对象来检测循环引用,如果它们相等,它认为有圆。

当然,我可以像许多人一样定义一个带有 Equals 的类和另一个用于序列化的类,然后将数据从一个类复制到另一个类中,但我希望能够在一个类中这样做以避免parallel class hierarchies作为code smells之一。

我希望能够定义 Equals、GetHashCode 并保留它Serializable。怎么样?

【问题讨论】:

  • OnSerializing 和 OnSerialized 属性有助于在序列化开始/结束时设置标志和禁用/启用方法行为,但它不适用于 WebMethods 结果 SOAP/Xml 序列化程序。
  • 你能举一个简单的小例子来说明这个问题吗?请包括导致问题的EqualsGetHashCode 实现。
  • 另外,如果您使用的是 ASMX Web 服务,那么 [Serializable] 一点也不重要。
  • 我正在使用一个类来支持 System.Runtime.Serialization.Json.DataContractJsonSerializer 和 Xml WebMethod 序列化。到目前为止,它一直有效,直到我定义了 Equals 和 GetHashCode 以便能够将它们放入字典并合并 dups。

标签: .net xml web-services serialization xml-serialization


【解决方案1】:

让我深入了解问题的根源。 因为我有同样的问题。

我意识到,问题不在 GetHashCode 方法中,而是在 Equals 方法中。

XmlSerializer 引发此类异常的最重要原因是 XML 文档的结构和 XmlSerializer 内部的“平等机制”。

例如:

private XmlSerializer serializer;

public void TryToSerialize(TextWriter output)
{
    MyObject instance = new MyObject();
    instance.Key = 101;
    instance.SomeValue = "Some value";
    instance.Child = new MyObject();
    instance.Child.Key = 101;
    instance.Child.SomeValue = "Another value";

    serializer.Serialize(output, instance);
}

我是如何实现 GetHashCode 方法和 Equals 方法的?

像这样:

public overrides int GetHashCode()
{
    return this.Key.GetHashCode();
}

public overrides bool Equals(object obj)
{
    if(obj == null) return false;

    MyObject other = obj as MyObject;
    if(other == null) return false;

    return this.Key.Equals(other.Key);
}

如果我运行“TryToSerialize”方法会发生什么? 我会得到一个 InvalidOperationException 消息在序列化 T 类型的对象时检测到循环引用

在序列化时,XmlSerializer 会尽量避免将与子对象相同的对象添加到 XML 文档中,因为这会导致循环。 但是检查“这是同一个对象”的方法是使用 GetHashCode 方法和 Equals 方法来检查它们的设计目的 - 检查对象的相等性。

在我们的示例中,这些对象可能是不同的实例,并且 XmlSerializer 不会检查“内存中的实例”,而是使用它知道的方法 - GetHashCode 和 Equals。

所以,想想你如何实现你的Equals-method

或者更好...

想一想如何改进类和方法的实现,从而在问题的根源上避免这个问题。 ;-)

【讨论】:

    【解决方案2】:

    一种方法是从一个接口继承涉及序列化的每个类,该接口定义 OnSerializing 方法并在每个类中实现该方法,以便为符合条件的子级调用它。在每个类中也有序列化私有布尔成员,默认为 false,并在 OnSerializing 中将其设置为 true。这将允许在根上调用它并传播到所有可序列化的节点,直到每个叶子。

    在从 WebMethod 返回可序列化的 returnValue 之前调用 returnValue.OnSerializing()。

    每次覆盖 Equals 时第一行应该是if(Serializing)return base.Equals(obj);,GetHashCode 中的第一行应该是if(Serializing)return base.GetHashCode();

    在从 WebMethod 返回之前通过调用 OnSerializing 标记整个树以禁用 Equals 和 GetHashCode 的自定义。

    如果您需要在序列化后做一些事情(例如保存在磁盘上然后返回),那么定义 OnSerialized 并以同样的方式通过树传播将 Serializing 标志设置为 false。

    当然,如果可能,只需从一个类而不是接口继承所有可序列化对象,并在那里实现所有内容,以减少每个可序列化类的实现开销。

    【讨论】:

      【解决方案3】:

      在我的情况下,我解决了正确实现(自动视觉工作室代码完成)方法 Equals 和 GetHashCode,如下所示:

       public override bool Equals(object obj)
              {
                  var permission = obj as Permission;
                  return permission != null &&
                         EqualityComparer<AccessLevel>.Default.Equals(Access, permission.Access) &&
                         EqualityComparer<Functionality>.Default.Equals(Function, permission.Function);
              }
      
              public override int GetHashCode()
              {
                  var hashCode = -720887508;
                  hashCode = hashCode * -1521134295 + EqualityComparer<AccessLevel>.Default.GetHashCode(Access);
                  hashCode = hashCode * -1521134295 + EqualityComparer<Functionality>.Default.GetHashCode(Function);
                  return hashCode;
              }
      

      以前的代码只有 Equals 方法,就像这样:

      return (Access.Name.Equals((Permission)obj.Name)...
      

      【讨论】:

        猜你喜欢
        • 2017-08-05
        • 1970-01-01
        • 2016-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多