c# - How to avoid reference comparison with self implemented value types -
i'm trying implement value type in general mimics behavior of type short.
so far comparison , assignments between value type , short working out fine, when boxing jumps in problems starts.
below can find unit tests illustrating problem source value type.
the first assert method uses overriden equals method of value type, second assert result in reference comparison (runtimehelpers.equals(this, obj);) of course fails.
the test:
[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 }
the value type:
[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 myvaluetype) return _value - ((myvaluetype)value)._value; if (value short) return _value - ((short)value); throw new argumentexception("argument must 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 short? || _value == obj 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; } }
edit: added complete lists of unit tests, failing asserts @ bottom , marked
[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 } }
you can't this. basically, there's no way of symmetrically implementing equals
between 2 types unless know each other.
note assertion:
assert.istrue(short_type.equals(valuetype));
works due implicit conversion. it's effectively using:
short converted = valuetype; assert.istrue(short_type.equals(converted));
that's not same failing assertion, passing boxed myvaluetype
int16.equals(object)
.
i doubt it's performing reference comparison - calling int16.equals(object)
return false
.
fundamentally, give on hope - type oddly-designed, in terms of implementing iequatable<short>
not iequatable<myvaluetype>
etc. behave in way unexpected many developers. should revisit whole design.
Comments
Post a Comment