gpt4 book ai didi

c# - MS覆盖Equals()准则无法达到自己的标准。改为使用最佳做法?

转载 作者:太空宇宙 更新时间:2023-11-03 19:52:52 27 4
gpt4 key购买 nike

我正在查看Microsoft提出的超越Equals运算符的准则。
https://msdn.microsoft.com/en-us/library/ms173147(v=vs.90).aspx

他们指出:


  平等的新实施应遵循以下所有保证
  等于:
  
  
  x.Equals(x)返回true。
  x.Equals(y)返回与y.Equals(x)相同的值。
  如果(x.Equals(y)&& y.Equals(z))返回true,则x.Equals(z)返回true。
  只要不修改x和y引用的对象,x.Equals(y)的连续调用将返回相同的值。
  x.Equals(null)返回false(仅适用于不可为null的值类型。)
  


他们接着介绍了一个基类和子类(TwoDPoint和ThreeDPoint,下面的代码)的示例,这些示例实现了重写此方法的最佳实践。

但是,这两个示例类均未通过刚刚给出的“平等保证”。 IE,TwoDPoint.Equals(ThreeDPoint)可以返回true,但是ThreeDPoint.Equals(TwoDPoint)始终返回false。这使上面的第二个要点失效。

    static void Main(string[] args)
{
TwoDPoint twoDPoint = new TwoDPoint(1, 2);
ThreeDPoint threeDPoint = new ThreeDPoint(1, 2, 3);

//this will assert because twoDPoint.Equals(threeDPoint) == true
//but, threeDPoint.Equals(twoDPoint) == false
AssertMicrosoftEqualsGuidelines(twoDPoint, threeDPoint, null);
}

/// <summary>
/// Will Assert() if any of microsofts rules for Equals overriding fail.
/// NOTE, x and y can not be null.
/// https://msdn.microsoft.com/en-us/library/ms173147(v=vs.90).aspx
/// </summary>
static void AssertMicrosoftEqualsGuidelines(object x, object y, object z)
{
System.Diagnostics.Debug.Assert(x.Equals(x), "FAILED x.Equals(x) returns true.");
System.Diagnostics.Debug.Assert(x.Equals(y) == y.Equals(x), "FAILED x.Equals(y) returns the same value as y.Equals(x).");

if(x.Equals(y) && y.Equals(z))
{
System.Diagnostics.Debug.Assert(x.Equals(z), "FAILED Successive invocations of x. Equals (y) return the same value as long as the objects referenced by x and y are not modified.");
}

System.Diagnostics.Debug.Assert(x.Equals(y) == x.Equals(y) == x.Equals(y) == x.Equals(y), "Successive invocations of x. Equals (y) return the same value as long as the objects referenced by x and y are not modified.");
System.Diagnostics.Debug.Assert(x.Equals(null) == false, "x.Equals (null) returns false");
}
}

class TwoDPoint : System.Object
{
public readonly int x, y;

public TwoDPoint(int x, int y) //constructor
{
this.x = x;
this.y = y;
}

public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}

// If parameter cannot be cast to Point return false.
TwoDPoint p = obj as TwoDPoint;
if ((System.Object)p == null)
{
return false;
}

// Return true if the fields match:
return (x == p.x) && (y == p.y);
}

public bool Equals(TwoDPoint p)
{
// If parameter is null return false:
if ((object)p == null)
{
return false;
}

// Return true if the fields match:
return (x == p.x) && (y == p.y);
}

public override int GetHashCode()
{
return x ^ y;
}
}

class ThreeDPoint : TwoDPoint
{
public readonly int z;

public ThreeDPoint(int x, int y, int z)
: base(x, y)
{
this.z = z;
}

public override bool Equals(System.Object obj)
{
// If parameter cannot be cast to ThreeDPoint return false:
ThreeDPoint p = obj as ThreeDPoint;
if ((object)p == null)
{
return false;
}

// Return true if the fields match:
return base.Equals(obj) && z == p.z;
}

public bool Equals(ThreeDPoint p)
{
// Return true if the fields match:
return base.Equals((TwoDPoint)p) && z == p.z;
}

public override int GetHashCode()
{
return base.GetHashCode() ^ z;
}
}


那么“平等保证”的准则是错误的吗?人们应该否决等于的对象也检查两个对象的类型是否相同? IE浏览器

if(GetType() != obj.GetType()){return false;} //include in Equals()?


真的,我想这可以归结为,如果所有基类字段都匹配,则让基类的equals()方法为子类返回true是否被认为是“可以”?从基类的角度来看,恕我直言是有道理的,但是,您最终违反了上面的规则2。违反该规则会对诸如字典和哈希集之类的对象产生什么影响。您是否会问一些细微的错误?

最佳答案

这些例子是不好的。在MSDN中,您会经常看到这种情况-它们通常只关注一件事,而忽略其他所有内容。

平等是一个棘手的概念,它与继承关系不大(您会经常看到“与继承关系不大” –继承很棘手)。如前所述,关于平等的思考主要有两种方式:价值平等和引用平等。有趣的是,两者可以重叠也可以不重叠。

引用相等是更简单的一种。它与继承非常吻合,因为它不会对要比较的对象做任何解释-引用相同或不同。所有准则均适用于参考相等性。

价值平等要复杂得多,更重要的是,平等与身份之间存在一些重叠。

说到严格的价值平等,它就不能与继承,句号一起使用。使用结构实现严格的值相等非常容易,特别是如果您遵循结构设计的所有准则。没有继承,理想情况下,您的结构是不可变的。这同样适用于匿名类型-这就是为什么它们可以承受默认情况下具有值相等的原因。局限性使其变得相当简单。

如果两个对象不是同一类型,则它们根本不能严格相等。因此,确实,为了获得最佳效果,您永远都不应该允许myClass.Equals(subclass)或其他方式。这对于依赖Equals正确行为的其他代码(例如哈希集)非常重要。

由于在某些情况下,除了严格的相等之外还可以做一些实际的事情,因此人们编写了许多不同的比较方法。也许您关心的是对象的ID,而不关心其他字段-身份。也许您想看看是否需要更新数据库中的对象。有些人重写了Equals方法以提供此功能,这完全是错误的。如果您遇到这样的问题,请自行制定方法。并非.NET限制您一个类可以拥有多少个方法或接口:)

当您查看MSDN在此示例中使用的类型时,可以看到该概念多么荒谬。首先,这些不应该是子类!任何3D点都不能等于2D点,并且3D点永远不能替代2D点。这不仅违反了Equals准则,还违反了常见的对象设计实践。它使用继承进行代码重用,但这不是对象设计的好方法。子类应该始终是其祖先类的有效替代者,显然这里不是这种情况。

人们会犯错误。编写MSDN的人也这样做。您会发现.NET BCL有很多错误的地方-也许它们在某一时刻是合情合理的,也许做法已更改,或者设计它们的人都做错了。这种情况一直存在,您必须为此做好准备。练习不是一成不变的,它们始终是上下文相关的-您必须理解其推理,以便您可以选择它们对于给定场景是否有意义。现在问自己,您是否希望哈希集类认为2D点等于3D点?试想这样的代码:

var set = new HashSet<2DPoint>();
set.Add(new 2DPoint(3, 3));
set.Add(new 3DPoint(3, 3, 0));


第二个 Add是否应该失败?如果这是一种添加或更新方法,并且您认为自己添加了3DPoint却实际上只是保留了“相等”的旧2DPoint怎么办?您的代码对获得2DPoint而不是3DPoint有多高兴?

如果您需要的不是严格的值相等或引用相等,只需添加自己的方法即可。或您自己的界面。但是,不要仅仅因为它已经存在就“重用” Equals-它的界面非常清晰,而且您违反了它。它与以与 IComparable<T>不符的方式实现 IComparable<T>几乎没有什么不同-它只是看起来“不同”,因为接口是隐式的。但这仍然是您必须遵循的接口。仅因为接口具有与所需接口相似的方法签名,所以重用接口是很糟糕的。而且我过去也对此感到内--每当需要 ThreadStart委托时都使用 void ()委托(另一方面, Action完全可以-您没有违反“执行一些不返回任何值且不带参数的操作”)。

关于c# - MS覆盖Equals()准则无法达到自己的标准。改为使用最佳做法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36648246/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com