没有默认的成员相等性,但对于基值类型 (float
, byte
, decimal
等),语言规范要求按位比较。 JIT 优化器将此优化为正确的汇编指令,但从技术上讲,此行为等同于 Cmemcmp
功能。
一些 BCL 示例
-
DateTime
简单比较一下其内部InternalTicks
成员字段,很长;
-
PointF
比较 X 和 Y,如下所示(left.X == right.X) && (left.Y == right.Y)
;
-
Decimal
不比较内部字段,而是回退到InternalImpl,这意味着它位于内部不可见的.NET部分(但您可以检查SSCLI);
-
Rectangle
显式比较每个字段(x、y、宽度、高度);
-
ModuleHandle
使用它的Equals
override 并且还有更多这样做;
-
SqlString
和其他 SqlXXX 结构使用其IComparable.Compare
执行;
-
Guid
是这个列表中最奇怪的:它有自己的短路长列表 if 语句比较每个内部字段(_a
to _k
, all int) 表示不相等,不相等时返回 false。如果全部不相等,则返回 true。
结论
这个列表相当任意,但我希望它能对这个问题有所启发:没有可用的默认方法,甚至 BCL 根据其目的对每个结构使用不同的方法。底线似乎是后来的添加更频繁地调用它们Equals
覆盖或Icomparable.Compare
,但这只是将问题转移到另一种方法。
其他方法:
您可以使用反射来遍历每个字段,但这非常慢。您还可以创建单个扩展方法或静态助手来对内部字段进行按位比较。使用StructLayout.Sequential
,获取内存地址和大小,并比较内存块。这需要不安全的代码,但它快速、简单(并且有点脏)。
Update:改写,添加一些实际例子,添加新结论
更新:成员比较的实现
上面的内容显然是对这个问题的轻微误解,但我把它留在那里,因为我认为它无论如何对未来的访问者都有一定的价值。这是一个更切题的答案:
这是对象和值类型的成员比较的实现,它可以递归地遍历所有属性、字段和可枚举内容,无论深度如何。它未经测试,可能包含一些拼写错误,但可以正常编译。更多详情请参阅代码中的注释:
public static bool MemberCompare(object left, object right)
{
if (Object.ReferenceEquals(left, right))
return true;
if (left == null || right == null)
return false;
Type type = left.GetType();
if (type != right.GetType())
return false;
if(left as ValueType != null)
{
// do a field comparison, or use the override if Equals is implemented:
return left.Equals(right);
}
// check for override:
if (type != typeof(object)
&& type == type.GetMethod("Equals").DeclaringType)
{
// the Equals method is overridden, use it:
return left.Equals(right);
}
// all Arrays, Lists, IEnumerable<> etc implement IEnumerable
if (left as IEnumerable != null)
{
IEnumerator rightEnumerator = (right as IEnumerable).GetEnumerator();
rightEnumerator.Reset();
foreach (object leftItem in left as IEnumerable)
{
// unequal amount of items
if (!rightEnumerator.MoveNext())
return false;
else
{
if (!MemberCompare(leftItem, rightEnumerator.Current))
return false;
}
}
}
else
{
// compare each property
foreach (PropertyInfo info in type.GetProperties(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.GetProperty))
{
// TODO: need to special-case indexable properties
if (!MemberCompare(info.GetValue(left, null), info.GetValue(right, null)))
return false;
}
// compare each field
foreach (FieldInfo info in type.GetFields(
BindingFlags.GetField |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance))
{
if (!MemberCompare(info.GetValue(left), info.GetValue(right)))
return false;
}
}
return true;
}
Update:修复了一些错误,添加了重写的使用Equals
当且仅当可用时
Update: object.Equals
不应被视为覆盖、固定。