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

时间:2015-04-14 17:51:14

标签: c# .net equals value-type

我正在尝试实现一般模仿short类型行为的值类型。

到目前为止,我的值类型和短路之间的比较和分配工作正常,但是当拳击跳跃问题开始时。

您可以在下面找到说明问题的单元测试以及我的值类型的来源。

第一个assert方法使用我的值类型的覆盖Equals方法,第二个assert实际上导致引用比较(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
    }
}

1 个答案:

答案 0 :(得分:4)

你不能这样做。基本上,除非他们彼此了解,否则无法在两种类型之间对称地实现Equals

请注意这个断言:

Assert.IsTrue(SHORT_TYPE.Equals(valueType));

由于隐式转换而起作用。它有效使用:

short converted = valueType;
Assert.IsTrue(SHORT_TYPE.Equals(converted));

与失败的断言相同,即将框MyValueType传递给Int16.Equals(object)

我怀疑它是否正在执行参考比较 - 它将调用Int16.Equals(object),它将返回false

从根本上说,我会放弃这个希望 - 你的类型绝对是奇怪的设计,在实施IEquatable<short>而不是IEquatable<MyValueType>等方面。这将以一种对许多人来说意外的方式表现开发人员。你应该重新审视整个设计。