如何避免与自我实现的值类型进行引用比较
How to avoid reference comparison with self implemented value types
我正在尝试实现一种通常模仿 short 类型行为的值类型。
到目前为止,我的值类型和 short 之间的比较和分配工作正常,但是当拳击跳转时,问题就开始了。
您可以在下面找到说明问题的单元测试以及我的值类型的来源。
第一个断言方法使用我的值类型的覆盖 Equals 方法,第二个断言实际上导致引用比较 (RuntimeHelpers.Equals(this, obj);) 当然失败了。
测试:
[TestMethod]
public void EqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = SHORT_TYPE;
object shortObject = SHORT_TYPE;
object valueObject = valueType;
Assert.IsTrue(valueObject.Equals(shortObject)); // success
Assert.IsTrue(shortObject.Equals(valueObject)); // failed, comparing 2 references
}
值类型:
[ComVisible(true)]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
[DebuggerDisplay("{_Value}")]
public struct MyValueType : IComparable, IFormattable, IConvertible, IComparable<short>, IEquatable<short>, IEquatable<MyValueType>
{
readonly short _Value;
public int CompareTo(Object value)
{
if (value == null)
return 1;
if (value is MyValueType)
return _Value - ((MyValueType)value)._Value;
if (value is short)
return _Value - ((short)value);
throw new ArgumentException("argument must be MyValueType");
}
public int CompareTo(short value) { return _Value - value; }
public int CompareTo(MyValueType myValue) { return _Value - myValue._Value; }
public override bool Equals(Object obj) { return _Value == obj as short? || _Value == obj as MyValueType?; }
public bool Equals(MyValueType obj) { return _Value == obj._Value; }
public bool Equals(short obj) { return _Value == obj; }
public override int GetHashCode() { return ((ushort)_Value | (_Value << 16)); }
[SecuritySafeCritical]
public override String ToString() { return _Value.ToString(); }
[SecuritySafeCritical]
public String ToString(IFormatProvider provider) { return _Value.ToString(provider); }
public String ToString(String format) { return ToString(format, NumberFormatInfo.CurrentInfo); }
public String ToString(String format, IFormatProvider provider) { return ToString(format, NumberFormatInfo.GetInstance(provider)); }
[SecuritySafeCritical]
String ToString(String format, NumberFormatInfo info) { return _Value.ToString(format, info); }
public static MyValueType Parse(String s) { return Parse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo); }
public static MyValueType Parse(String s, NumberStyles style) { return short.Parse(s, style); }
public static MyValueType Parse(String s, IFormatProvider provider) { return Parse(s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider)); }
public static MyValueType Parse(String s, NumberStyles style, IFormatProvider provider) { return short.Parse(s, style, provider); }
static MyValueType Parse(String s, NumberStyles style, NumberFormatInfo info) { return short.Parse(s, style, info); }
public TypeCode GetTypeCode() { return TypeCode.Int16; }
bool IConvertible.ToBoolean(IFormatProvider provider) { return Convert.ToBoolean(_Value); }
char IConvertible.ToChar(IFormatProvider provider) { return Convert.ToChar(_Value); }
sbyte IConvertible.ToSByte(IFormatProvider provider) { return Convert.ToSByte(_Value); }
byte IConvertible.ToByte(IFormatProvider provider) { return Convert.ToByte(_Value); }
short IConvertible.ToInt16(IFormatProvider provider) { return _Value; }
ushort IConvertible.ToUInt16(IFormatProvider provider) { return Convert.ToUInt16(_Value); }
int IConvertible.ToInt32(IFormatProvider provider) { return Convert.ToInt32(_Value); }
uint IConvertible.ToUInt32(IFormatProvider provider) { return Convert.ToUInt32(_Value); }
long IConvertible.ToInt64(IFormatProvider provider) { return Convert.ToInt64(_Value); }
ulong IConvertible.ToUInt64(IFormatProvider provider) { return Convert.ToUInt64(_Value); }
float IConvertible.ToSingle(IFormatProvider provider) { return Convert.ToSingle(_Value); }
double IConvertible.ToDouble(IFormatProvider provider) { return Convert.ToDouble(_Value); }
Decimal IConvertible.ToDecimal(IFormatProvider provider) { return Convert.ToDecimal(_Value); }
DateTime IConvertible.ToDateTime(IFormatProvider provider) { throw new InvalidCastException(); }
Object IConvertible.ToType(Type type, IFormatProvider provider) { throw new NotImplementedException(); }
bool IEquatable<short>.Equals(short other) { return _Value.Equals(other); }
public MyValueType(short value) { _Value = value; }
public static implicit operator MyValueType(short value) { return new MyValueType(value); }
public static implicit operator short(MyValueType myValueType) { return myValueType._Value; }
//public static explicit operator MyValueType(short value) { return new MyValueType(value); }
//public static explicit operator short(MyValueType myValueType) { return myValueType; }
//public static bool operator ==(MyValueType first, MyValueType second) { return first._Value == second._Value; }
//public static bool operator !=(MyValueType first, MyValueType second) { return first._Value != second._Value; }
}
编辑:添加了完整的单元测试列表,失败的断言在底部并标记
[TestClass]
public class MyValueTypeTest
{
[TestMethod]
public void AssignShortTest()
{
MyValueType valueType = 1;
short shortType = valueType;
Assert.IsTrue(shortType == 1);
}
[TestMethod]
public void AssignValueTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = SHORT_TYPE;
Assert.IsTrue(valueType == 1);
}
[TestMethod]
public void DictionaryShortTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Dictionary<short, string> dict = new Dictionary<short, string>();
dict.Add(SHORT_TYPE, "short");
Assert.IsTrue(dict.ContainsKey(valueType));
}
[TestMethod]
public void DictionaryValueTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Dictionary<MyValueType, string> dict = new Dictionary<MyValueType, string>();
dict.Add(valueType, "value");
Assert.IsTrue(dict.ContainsKey(SHORT_TYPE));
}
[TestMethod]
public void EqualsOperatorTest()
{
const short SHORT_TYPE_A = 1;
MyValueType valueTypeA = 1;
MyValueType valueTypeB = 1;
Assert.IsTrue(valueTypeA == valueTypeB);
Assert.IsTrue(SHORT_TYPE_A == valueTypeA);
Assert.IsTrue(valueTypeA == SHORT_TYPE_A);
}
[TestMethod]
public void ShortEqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.IsTrue(SHORT_TYPE.Equals(valueType));
}
public static bool Test(Object objA, Object objB)
{
if (objA == objB)
return true;
if (objA == null || objB == null)
return false;
return objA.Equals(objB);
}
[TestMethod]
public void ValueEqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.IsTrue(valueType.Equals(SHORT_TYPE));
}
[TestMethod]
public void AreEqualTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.AreEqual(valueType, SHORT_TYPE, "test 1"); // success
Assert.AreEqual(SHORT_TYPE, valueType, "test 2"); // failed, comparing 2 references
}
[TestMethod]
public void ObjectEqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.IsTrue(object.Equals(valueType, SHORT_TYPE), "test 1"); // success
Assert.IsTrue(object.Equals(SHORT_TYPE, valueType), "test 2"); // failed, comparing 2 references
}
[TestMethod]
public void EqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = SHORT_TYPE;
object shortObject = SHORT_TYPE;
object valueObject = valueType;
Assert.IsTrue(valueObject.Equals(shortObject), "test 1"); // success
Assert.IsTrue(shortObject.Equals(valueObject), "test 2"); // failed, comparing 2 references
}
}
你不能这样做。基本上,没有办法在两种类型之间对称地实现 Equals
,除非它们彼此了解。
注意这个断言:
Assert.IsTrue(SHORT_TYPE.Equals(valueType));
由于隐式转换而有效。 有效 使用:
short converted = valueType;
Assert.IsTrue(SHORT_TYPE.Equals(converted));
这与您的失败断言不相同,它将盒装 MyValueType
传递给 Int16.Equals(object)
。
我怀疑它是否在执行参考比较 - 它将调用 Int16.Equals(object)
,这将 return false
.
从根本上说,我会放弃这个希望 - 你的类型绝对设计得很奇怪,在实现 IEquatable<short>
而不是 IEquatable<MyValueType>
等方面。这将以一种意想不到的方式表现对于许多开发人员。您应该重新审视整个设计。
我正在尝试实现一种通常模仿 short 类型行为的值类型。
到目前为止,我的值类型和 short 之间的比较和分配工作正常,但是当拳击跳转时,问题就开始了。
您可以在下面找到说明问题的单元测试以及我的值类型的来源。
第一个断言方法使用我的值类型的覆盖 Equals 方法,第二个断言实际上导致引用比较 (RuntimeHelpers.Equals(this, obj);) 当然失败了。
测试:
[TestMethod]
public void EqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = SHORT_TYPE;
object shortObject = SHORT_TYPE;
object valueObject = valueType;
Assert.IsTrue(valueObject.Equals(shortObject)); // success
Assert.IsTrue(shortObject.Equals(valueObject)); // failed, comparing 2 references
}
值类型:
[ComVisible(true)]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
[DebuggerDisplay("{_Value}")]
public struct MyValueType : IComparable, IFormattable, IConvertible, IComparable<short>, IEquatable<short>, IEquatable<MyValueType>
{
readonly short _Value;
public int CompareTo(Object value)
{
if (value == null)
return 1;
if (value is MyValueType)
return _Value - ((MyValueType)value)._Value;
if (value is short)
return _Value - ((short)value);
throw new ArgumentException("argument must be MyValueType");
}
public int CompareTo(short value) { return _Value - value; }
public int CompareTo(MyValueType myValue) { return _Value - myValue._Value; }
public override bool Equals(Object obj) { return _Value == obj as short? || _Value == obj as MyValueType?; }
public bool Equals(MyValueType obj) { return _Value == obj._Value; }
public bool Equals(short obj) { return _Value == obj; }
public override int GetHashCode() { return ((ushort)_Value | (_Value << 16)); }
[SecuritySafeCritical]
public override String ToString() { return _Value.ToString(); }
[SecuritySafeCritical]
public String ToString(IFormatProvider provider) { return _Value.ToString(provider); }
public String ToString(String format) { return ToString(format, NumberFormatInfo.CurrentInfo); }
public String ToString(String format, IFormatProvider provider) { return ToString(format, NumberFormatInfo.GetInstance(provider)); }
[SecuritySafeCritical]
String ToString(String format, NumberFormatInfo info) { return _Value.ToString(format, info); }
public static MyValueType Parse(String s) { return Parse(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo); }
public static MyValueType Parse(String s, NumberStyles style) { return short.Parse(s, style); }
public static MyValueType Parse(String s, IFormatProvider provider) { return Parse(s, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider)); }
public static MyValueType Parse(String s, NumberStyles style, IFormatProvider provider) { return short.Parse(s, style, provider); }
static MyValueType Parse(String s, NumberStyles style, NumberFormatInfo info) { return short.Parse(s, style, info); }
public TypeCode GetTypeCode() { return TypeCode.Int16; }
bool IConvertible.ToBoolean(IFormatProvider provider) { return Convert.ToBoolean(_Value); }
char IConvertible.ToChar(IFormatProvider provider) { return Convert.ToChar(_Value); }
sbyte IConvertible.ToSByte(IFormatProvider provider) { return Convert.ToSByte(_Value); }
byte IConvertible.ToByte(IFormatProvider provider) { return Convert.ToByte(_Value); }
short IConvertible.ToInt16(IFormatProvider provider) { return _Value; }
ushort IConvertible.ToUInt16(IFormatProvider provider) { return Convert.ToUInt16(_Value); }
int IConvertible.ToInt32(IFormatProvider provider) { return Convert.ToInt32(_Value); }
uint IConvertible.ToUInt32(IFormatProvider provider) { return Convert.ToUInt32(_Value); }
long IConvertible.ToInt64(IFormatProvider provider) { return Convert.ToInt64(_Value); }
ulong IConvertible.ToUInt64(IFormatProvider provider) { return Convert.ToUInt64(_Value); }
float IConvertible.ToSingle(IFormatProvider provider) { return Convert.ToSingle(_Value); }
double IConvertible.ToDouble(IFormatProvider provider) { return Convert.ToDouble(_Value); }
Decimal IConvertible.ToDecimal(IFormatProvider provider) { return Convert.ToDecimal(_Value); }
DateTime IConvertible.ToDateTime(IFormatProvider provider) { throw new InvalidCastException(); }
Object IConvertible.ToType(Type type, IFormatProvider provider) { throw new NotImplementedException(); }
bool IEquatable<short>.Equals(short other) { return _Value.Equals(other); }
public MyValueType(short value) { _Value = value; }
public static implicit operator MyValueType(short value) { return new MyValueType(value); }
public static implicit operator short(MyValueType myValueType) { return myValueType._Value; }
//public static explicit operator MyValueType(short value) { return new MyValueType(value); }
//public static explicit operator short(MyValueType myValueType) { return myValueType; }
//public static bool operator ==(MyValueType first, MyValueType second) { return first._Value == second._Value; }
//public static bool operator !=(MyValueType first, MyValueType second) { return first._Value != second._Value; }
}
编辑:添加了完整的单元测试列表,失败的断言在底部并标记
[TestClass]
public class MyValueTypeTest
{
[TestMethod]
public void AssignShortTest()
{
MyValueType valueType = 1;
short shortType = valueType;
Assert.IsTrue(shortType == 1);
}
[TestMethod]
public void AssignValueTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = SHORT_TYPE;
Assert.IsTrue(valueType == 1);
}
[TestMethod]
public void DictionaryShortTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Dictionary<short, string> dict = new Dictionary<short, string>();
dict.Add(SHORT_TYPE, "short");
Assert.IsTrue(dict.ContainsKey(valueType));
}
[TestMethod]
public void DictionaryValueTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Dictionary<MyValueType, string> dict = new Dictionary<MyValueType, string>();
dict.Add(valueType, "value");
Assert.IsTrue(dict.ContainsKey(SHORT_TYPE));
}
[TestMethod]
public void EqualsOperatorTest()
{
const short SHORT_TYPE_A = 1;
MyValueType valueTypeA = 1;
MyValueType valueTypeB = 1;
Assert.IsTrue(valueTypeA == valueTypeB);
Assert.IsTrue(SHORT_TYPE_A == valueTypeA);
Assert.IsTrue(valueTypeA == SHORT_TYPE_A);
}
[TestMethod]
public void ShortEqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.IsTrue(SHORT_TYPE.Equals(valueType));
}
public static bool Test(Object objA, Object objB)
{
if (objA == objB)
return true;
if (objA == null || objB == null)
return false;
return objA.Equals(objB);
}
[TestMethod]
public void ValueEqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.IsTrue(valueType.Equals(SHORT_TYPE));
}
[TestMethod]
public void AreEqualTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.AreEqual(valueType, SHORT_TYPE, "test 1"); // success
Assert.AreEqual(SHORT_TYPE, valueType, "test 2"); // failed, comparing 2 references
}
[TestMethod]
public void ObjectEqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = 1;
Assert.IsTrue(object.Equals(valueType, SHORT_TYPE), "test 1"); // success
Assert.IsTrue(object.Equals(SHORT_TYPE, valueType), "test 2"); // failed, comparing 2 references
}
[TestMethod]
public void EqualsTest()
{
const short SHORT_TYPE = 1;
MyValueType valueType = SHORT_TYPE;
object shortObject = SHORT_TYPE;
object valueObject = valueType;
Assert.IsTrue(valueObject.Equals(shortObject), "test 1"); // success
Assert.IsTrue(shortObject.Equals(valueObject), "test 2"); // failed, comparing 2 references
}
}
你不能这样做。基本上,没有办法在两种类型之间对称地实现 Equals
,除非它们彼此了解。
注意这个断言:
Assert.IsTrue(SHORT_TYPE.Equals(valueType));
由于隐式转换而有效。 有效 使用:
short converted = valueType;
Assert.IsTrue(SHORT_TYPE.Equals(converted));
这与您的失败断言不相同,它将盒装 MyValueType
传递给 Int16.Equals(object)
。
我怀疑它是否在执行参考比较 - 它将调用 Int16.Equals(object)
,这将 return false
.
从根本上说,我会放弃这个希望 - 你的类型绝对设计得很奇怪,在实现 IEquatable<short>
而不是 IEquatable<MyValueType>
等方面。这将以一种意想不到的方式表现对于许多开发人员。您应该重新审视整个设计。