如何避免与自我实现的值类型进行引用比较

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> 等方面。这将以一种意想不到的方式表现对于许多开发人员。您应该重新审视整个设计。