42

今天我偶然发现了我写的一个有趣的错误。我有一组可以通过通用设置器设置的属性。这些属性可以是值类型或引用类型。

public void SetValue( TEnum property, object value )
{
    if ( _properties[ property ] != value )
    {
        // Only come here when the new value is different.
    }
}

在为此方法编写单元测试时,我发现值类型的条件始终为真。我很快就发现这是由于装箱/拆箱。我也没有花很长时间将代码调整为以下内容:

public void SetValue( TEnum property, object value )
{
    if ( !_properties[ property ].Equals( value ) )
    {
        // Only come here when the new value is different.
    }
}

问题是我对这个解决方案并不完全满意。我想保持一个简单的参考比较,除非值被装箱。

我正在考虑的当前解决方案只需要Equals()装箱值。检查盒装值似乎有点矫枉过正。没有更简单的方法吗?

4

5 回答 5

26

如果您在处理值类型时需要不同的行为,那么您显然需要执行某种测试。您不需要显式检查装箱值类型,因为所有值类型都将被装箱**,因为参数类型为object.

此代码应符合您规定的标准:如果value是(装箱的)值类型,则调用多态Equals方法,否则用于==测试引用是否相等。

public void SetValue(TEnum property, object value)
{
    bool equal = ((value != null) && value.GetType().IsValueType)
                     ? value.Equals(_properties[property])
                     : (value == _properties[property]);

    if (!equal)
    {
        // Only come here when the new value is different.
    }
}

( ** 而且,是的,我知道这Nullable<T>是一个值类型,它有自己的与装箱和拆箱有关的特殊规则,但这在这里几乎无关紧要。)

于 2011-06-02T00:10:09.370 回答
12

Equals() 通常是首选方法。

.Equals() 的默认实现对引用类型进行了简单的引用比较,因此在大多数情况下,这就是您将得到的。Equals() 可能已被覆盖以提供一些其他行为,但如果有人在类中覆盖了 .Equals() ,那是因为他们想要更改该类型的相等语义,如果你不这样做,最好让它发生有一个令人信服的理由不这样做。使用 == 绕过它可能会导致混淆,当您的班级认为两件事情不同时,而其他所有班级都同意它们是相同的。

于 2011-06-01T17:25:43.637 回答
1

由于输入参数的类型是object,因此您将始终在方法的上下文中获得一个装箱的值。

我认为您唯一的机会是更改方法的签名并编写不同的重载。

于 2011-06-01T17:23:09.433 回答
1

这个怎么样:

if(object.ReferenceEquals(first, second)) { return; }
if(first.Equals(second)) { return; }

// they must differ, right?

更新

我意识到这在某些情况下不能按预期工作:

  • 对于值类型,ReferenceEquals返回 false 所以我们回退到Equals,它的行为符合预期。
  • 对于ReferenceEquals返回 true 的引用类型,我们认为它们与预期的“相同”。
  • 对于ReferenceEquals返回 false 和Equals返回 false 的引用类型,我们按预期将它们视为“不同”。
  • 对于ReferenceEquals返回 false 和Equals返回 true 的引用类型,我们认为它们“相同”,即使我们想要“不同”

所以教训是“不要变聪明”

于 2011-06-01T17:24:52.890 回答
0

我想

我想保持一个简单的参考比较,除非值被装箱。

有点等价于

如果值被装箱,我会做一个非“简单的参考比较”。

这意味着您需要做的第一件事是检查该值是否已装箱。

如果存在检查对象是否为装箱值类型的方法,它应该至少与您提供链接的“过度杀伤”方法一样复杂,除非这不是最简单的方法。尽管如此,应该有一个“最简单的方法”来确定一个对象是否是一个装箱的值类型。这种“最简单的方法”不太可能比简单地使用对象 Equals() 方法更简单,但我已经为这个问题添加了书签,以防万一。

(不确定我是否合乎逻辑)

于 2011-06-01T17:36:08.017 回答