【问题标题】:Comparing boxed value types比较盒装值类型
【发布时间】:2011-09-06 11:54:16
【问题描述】:

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

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

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

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

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

我正在考虑的当前解决方案只是调用Equals() 来获取装箱值。做a check for a boxed values 似乎有点矫枉过正。没有更简单的方法吗?

【问题讨论】:

  • 当然,如果您想要盒装值的不同行为,那么您需要检查您是否正在处理盒装值?
  • 使用 T 类型对该方法进行泛型重载,其中 T : struct
  • @lukas,除非有比T 和约束更多的区别,否则将不起作用。
  • 我的意思是:把它分成两种方法。一个处理 ref 类型,另一个处理每个函数中的值类型和相应的逻辑。 @Steven 我有一个想法,但它非常危险。我的意思是我没有尝试过:P 如果您使用的是 C# 4.0,请使用对象的动态 insted。我假设你有一些性能理由去做你所做的事情,但这可能已经足够好了。

标签: c# equality boxing unboxing


【解决方案1】:

如果您在处理值类型时需要不同的行为,那么您显然需要执行某种测试。您不需要显式检查 boxed 值类型,因为所有值类型都将被装箱**,因为参数类型为 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> 是一个值类型,它有自己的与装箱和拆箱相关的特殊规则,但这在这里几乎无关紧要。)

【讨论】:

  • 谢谢,这似乎工作得很好,没有重大开销。我发现平均运行时间没有增加。
  • 拳击中的开销也调用 getType() 有成本。最好通过动态生成 getter 和 setter 委托来避免它。保持实际类型从不装箱。
【解决方案2】:

这个怎么样:

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 的引用类型,我们认为它们“相同”,即使我们想要“不同”

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

【讨论】:

  • 如果值只是被装箱了,就像Value类型的情况一样,第一个if总是会产生false,因此它不会解决OP问题。
  • 据我了解,问题是“我想保持简单的参考比较,除非值被装箱。”这会做到这一点。但正如肖恩的回答所说,“如果有人在一个类中重写了 .Equals() ,那是因为他们想要更改该类型的相等语义,如果你没有令人信服的理由不这样做,最好让这种情况发生” .我认为提问者有一个令人信服的理由。
  • 但是,我将不得不在其他时间验证这是否正确,现在必须走了。我相信 Simone 提出了一个有效的观点。
  • 您刚刚复制了内置 object.Equals(x,y) 静态方法的功能。这基本上首先检查引用是否相等,如果失败,它会调用多态 x.Equals(y) 实例方法。
【解决方案3】:

我想

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

有点等价于

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

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

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

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

【讨论】:

    【解决方案4】:

    Equals() 通常是首选方法。

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

    【讨论】:

    • 问题正是Equals 可能已被覆盖。尽管两个对象相等,但这并不意味着没有设置新对象(具有不同的引用)。
    【解决方案5】:

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

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

    【讨论】:

    • 谢谢,明天试试。
    猜你喜欢
    • 1970-01-01
    • 2016-02-15
    • 2012-10-10
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    相关资源
    最近更新 更多