【问题标题】:How do I compare two objects based on their base class?如何根据基类比较两个对象?
【发布时间】:2009-11-02 14:42:45
【问题描述】:

我希望能够比较从 C# 中的同一个抽象类派生的两个类。下面的代码说明了我的问题。

我现在可以通过将BaseClass 设为非抽象来修复代码,然后在ToBassClass() 中返回一个new BaseClass 对象。但就没有更优雅、更高效的解决方案吗?

abstract class BaseClass
{
   BaseClass(int x)
   {
       X = x;
   }

   int X { get; private set; }

   // It is probably not necessary to override Equals for such a simple class,
   // but I've done it to illustrate my point.
   override Equals(object other)
   {
       if (!other is BaseClass)
       {
           return false;
       }

       BaseClass otherBaseClass = (BaseClass)other;

       return (otherBaseClass.X == this.X);
   }

   BaseClass ToBaseClass()
   {
       // The explicit is only included for clarity.
       return (BaseClass)this;
   }
}

class ClassA : BaseClass
{
   ClassA(int x, int y)
       : base (x)
   {
       Y = y;
   }

   int Y { get; private set; }
}

class ClassB : BaseClass
{
   ClassB(int x, int z)
       : base (x)
   {
       Z = z;
   }

   int Z { get; private set; }
}

var a = new A(1, 2);
var b = new B(1, 3);

// This fails because despite the call to ToBaseClass(), a and b are treated
// as ClassA and ClassB classes so the overridden Equals() is never called.
Assert.AreEqual(a.ToBaseClass(), b.ToBaseClass());

【问题讨论】:

  • 另外,int Z { get;私人Z; } 应该是 int Z { get;私人套装; } ?

标签: c# oop abstract-class


【解决方案1】:

这取决于您要测试相等性的确切位置。显然,ClassAClassB 实例在该词的真正意义上永远不会“相等”,因此覆盖 Equals 以使其表现得像这样实际上可能会导致代码中出现一些奇怪的错误。

但如果您想根据特定标准比较它们,那么您可以实现一个适合您需求的特定IEqualityComparer(或多个比较器)。

所以,在这种情况下,你会:

/// <Summary>
/// Compares two classes based only on the value of their X property.
/// </Summary>
public class ComparerByX : IEqualityComparer<BaseClass>
{
     #region IEqualityComparer<BaseClass> Members

     public bool Equals(BaseClass a, BaseClass b)
     {
         return (a.X == b.X);
     }

     public int GetHashCode(BaseClass obj)
     {
         return obj.X.GetHashCode();
     }

     #endregion

}

[编辑]关于评论:

请注意,这与覆盖 Equals 方法没有任何关系。

但是您将能够像这样检查是否相等:

IEqualityComparer<BaseClass> comparer = new ComparerByX();
Assert.True(comparer.Equals(a, b));

一开始这似乎不是一件好事,但它给你带来了几个好处:

a) 您可以拥有任意数量的IEqualityComparer&lt;T&gt; 实现。根据具体情况,您可能会发现 Equals 覆盖毕竟不是那么好。然后,您可能会因此而破坏所有代码。

b)实际上有很多类使用IEqualityComparer&lt;T&gt;来比较项目。

例如,您可能希望将BaseClass 用作字典中的键。在这种情况下,您将使用接受IEqualityComparer&lt;T&gt;Dictionary&lt;Key,Value&gt; 构造函数重载:

Dictionary<BaseClass, SomeOtherClass> dictionary 
    = new Dictionary<BaseClass, SomeOtherClass>(new ComparerByX());

这样,字典将在键查找期间使用自定义的ComparerByX

另外,例如,如果您使用的是 LINQ,您可以查看Distinct() 方法示例。它还支持返回不同值的重载,但使用指定的自定义 IEqualityComparer 进行比较。

【讨论】:

  • 这会让a.ToBaseClass() == b.ToBaseClass(),并且仍然保持a != b吗?
【解决方案2】:

好吧,正如 Freed 指出的那样,在这里使用 Assert.True 有点奇怪 - 你的意思是 Assert.AreEqual 吗?如果是这样,我希望它能够工作(即使没有 ToBaseClass 调用),尽管它取决于测试框架。

不过,当涉及到继承时,平等是很棘手的。就个人而言,我会创建一个合适的IEqualityComparer&lt;BaseClass&gt;,它明确表示“我将测试对象的这个特定方面”——这意味着基本上不涉及继承。

【讨论】:

    【解决方案3】:

    首先,您的代码无法编译。其次,当您的代码被修复以便可以编译时(特别是,Assert.True 更改为 Assert.AreEqual),我看到了您所期望的结果。这是一件好事,因为这是正确的行为。但是你不能依赖不覆盖Object.Equals的继承者,所以如果你只想通过基类进行比较,那么你应该实现IEqualityComparer&lt;BaseClass&gt;

    这是您可能想要的代码版本,以便它可以编译:

    abstract class BaseClass {
        public BaseClass(int x) { X = x; }
    
        public int X { get; private set; }
    
        public override bool  Equals(object other) {
            if (!(other is BaseClass)) {
                return false; 
            }
    
            BaseClass otherBaseClass = (BaseClass)other;
            return (otherBaseClass.X == this.X);
        }
    
        public BaseClass ToBaseClass() {
            return (BaseClass)this;
        }
    }
    
    class ClassA : BaseClass {
        public ClassA(int x, int y) : base (x) {
            Y = y;
        }
    
        public int Y { get; private set; }
    }
    
    class ClassB : BaseClass {
        public ClassB(int x, int z) : base (x) {
            Z = z;
        }
    
        public int Z { get; private set; }
    }
    
    class Program {
        static void Main(string[] args) {
            var a = new ClassA(1, 2);
            var b = new ClassB(1, 3);
            Assert.AreEqual(a.ToBaseClass(), b.ToBaseClass());
        }
    }
    

    【讨论】:

      【解决方案4】:

      我强烈推荐使用 KellermanSoftware.CompareNetObjects(虽然我不是作者 - 它非常灵活、有效并且到目前为止没有错误!)。

      我做了同样的事情来比较基于基类的 2 个对象。

      1) 创建一个“BaseClassComparer”,它采用您要用于比较的类型:

      using System;
      using KellermanSoftware.CompareNetObjects;
      
      namespace Compare
      {
      
          /// <summary>
          /// This allows us to compare objects based on a particular class (ie so we can compare on base classes)
          /// </summary>
          public class BaseClassComparer : KellermanSoftware.CompareNetObjects.TypeComparers.ClassComparer
          {
      
      
              private readonly Type _compareType;
              internal BaseClassComparer(Type compareType, RootComparer rootComparer) : base(rootComparer)
              {
      
                  _compareType = compareType;
              }
      
              public override void CompareType(CompareParms parms)
              {
                  parms.Object1Type = _compareType;
                  parms.Object2Type = _compareType;
      
                  base.CompareType(parms);
              }
      
              public override bool IsTypeMatch(Type type1, Type type2)
              {
                  if (((_compareType.IsAssignableFrom(type1)) && (_compareType.IsAssignableFrom(type2)))) {
                      return true;
                  } else {
                      return false;
                  }
              }
          }
      }
      

      然后,将这个类添加到 Kellerman 比较器中:

      _compare = New CompareLogic
      _compare.Config.CustomComparers.Add(New BaseClassComparer(compareType, RootComparerFactory.GetRootComparer()))
      

      然后进行比较。只会检查/报告基本类型 (compareType) 之间的差异...

      【讨论】:

      • 我知道这已经很多年了,但我只是测试了这个,我无法让它与最新版本的 CompareNetObjects 一起工作。它比较整个对象,而不仅仅是对象的超类部分。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多