比较装箱值类型
今天我偶然发现了一个我写的有趣的bug。我有一套属性可以通过一个普通的setter来设置。这些属性可以是值类型或引用类型。比较装箱值类型
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似乎有点矫枉过正。没有更简单的方法吗?
如果你需要,当你有一个价值型交易那么你显然会需要进行某些测试的不同的行为。您不需要明确检查盒装的值类型,因为所有值类型都将被装箱**,因为参数被键入为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-02 20:51:04
开销是在拳击也调用getType()有成本。最好避免通过生成getter和setter委托来避免它。将事物保持为实际类型永不装箱。 – 2017-11-03 17:01:43
由于输入参数的类型为object
,因此您将始终在方法的上下文中获取装箱值。
我认为你唯一的机会是改变方法的签名并写出不同的重载。
谢谢,明天会再来一次。 – 2011-06-01 18:12:47
如何:
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,我们认为他们“同”即使我们想要的“不同”
,该课程是“不弄巧”
如果该值刚刚装箱,如在值类型的情况下,第一个“if”将总是产生错误,因此它不会解决OP问题。 – Simone 2011-06-01 17:34:18
这个问题,据我了解,“我想保留一个简单的参考比较,除非价值被装箱。”这将做到这一点。但是,正如肖恩的回答所言,“如果有人在类中重写了.Equals(),那是因为他们想要改变该类型的平等语义,如果没有令人信服的理由不会” 。我认为提问者有一个令人信服的理由。 – 2011-06-01 17:36:10
这是一个正确的假设。 ; p – 2011-06-01 18:05:28
等于给定类型()通常是首选方法。
.Equals()的默认实现为参考类型做了一个简单的参考比较,所以在大多数情况下,这就是你会得到的。 Equals()可能已被覆盖以提供其他一些行为,但是如果有人在类中重写了.Equals(),那是因为他们想要改变该类型的平等语义,最好让它发生,如果你不有一个令人信服的理由不会。使用==旁路它会导致混淆,当你的班级看到两件事情不同时,每个班级都认为他们是一样的。
问题恰恰在于'Equals'可能已被覆盖。尽管两个对象相同,但这并不意味着一个新的对象(带有不同的引用)没有设置。 – 2011-06-01 18:03:40
我想
我想保持一个简单的参考比较,除非值被装箱。
是有点相当于
如果值是盒装的,我会做一个非“简单引用比较”。
这意味着您需要做的第一件事是检查值是否被装箱。
如果存在一种方法来检查一个对象是否是盒装值类型,它应该至少与您提供链接的“过度杀伤”方法一样复杂,除非这不是最简单的方法。尽管如此,应该有一个“最简单的方法”来确定一个对象是否是盒装值类型。这个“最简单的方法”不太可能比简单地使用Equals()方法更简单,但我已经为这个问题添加了书签,以便以防万一。
(不知道我是合乎逻辑的)
当然,如果你想不同行为的盒装值,那么你将需要检查你是否正在处理盒装值? – LukeH 2011-06-01 17:19:39
使用类型T对此方法进行泛型重载,其中T:struct – 2011-06-01 17:23:56
@lukas将不起作用,除非存在更多与“T”和约束不同的区别。 – 2011-06-01 17:29:43