The doco for IComparable.CompareTo says:
By definition, any object compares greater than a null reference (Nothing in Visual Basic); and two null references compare equal to each other.
What of ValueType? See, it uses boxing to allow for derivation from Object, but is it really an Object? Perhaps yes. Perhaps no.
Anyway, it also says:
The parameter, obj, must be the same type as the class or value type that implements this interface; otherwise, an ArgumentException is thrown.
So, which is the stronger rule? If I pass a null reference for comparison against a ValueType then do I say “this parameter is not of correct type” (because a ValueType is not a 'reference type' and can not be allocated a 'null reference') or do I say “this boxed ValueType that is behaving like an Object will sort higher than this null reference”?
Lame!
Ballbags!
Why do I continually become un-amazed by .NET? You know, I *want* to love it, but it really isn't being very accommodating.
Resorting to implementation for my specification I find that SqlBoolean.CompareTo(..) will throw on a null reference. Of course, I found another bug in Mono (assuming that MS has the authoritative implementation). Also, the NullableTypes open source library behaves the same way that Mono does (but not suspiciously so).
Some people might accuse me of breaking ranks by 'going my own way', but in the face of all this, can you blame me?
Technically, based on the doco, each implementation could be considered correct. If I can get two 'correct' yet 'different' behaviours when implementing an 'interface' then what sort of a 'contract' have I defined? It wouldn't stand up in court that's for sure.
Perhaps Microsoft should get their legal team onto the framework design team, I doubt they would have let something as blatent as this slip through the cracks.
John.
Hello John,
the doco for IComparable.CompareTo cannot says that two null references compare equal to each other.
This is because CompareTo is an instance method (and not a static method) so to call the CompareTo you need at least one non null instance.
You cannot have two null reference also with Equals(Object) method for the same reason.
Why don't you write some code (as an NUnit test) to explain what's your expected behaveour?
Ciao from Italy (luKa)
Read it again, your comments aren't relevant to what I said.
I understand that CompareTo is an instance member. I'm not interested in comparing two null references, I'm interested in what to do when comparing a ValueType to a null reference via CompareTo().
The problem is pretty simple:
- null compares lower than an instance
- objects must be of the same type
- ValueType can't be null
- ValueType derives from Object
- Object can be null
I'm not writing code for this, but if you are interested check out the classes that I commented on. I.e. behaviour of the MS and Mono implementations of SqlBoolean.CompareTo().
I can't think of a situation where you would call CompareTo(null), but the behaviour isn't well defined, so you don't know what to expect.
John.
Um, I just re-read my last comment. Sorry if I was too short.
I blame having a black eye, grazed head, countless bruises and every single muscle in my body howling in pain. Yep, just got back from the rugby trials today.. :)
John.
Hello Jhon,
I know exactly what you mean: I played rugby till I was 18 ;-)
IComparable.CompareTo is used by .NET Framework internal code as in the Array.BinarySearch and Array.Sort methods and the SortedList class.
An instance of an Array or a SortedList can contain items of different types so one can be a value-types and oneother can be a reference-types. So it *is* really possible that IComparable.CompareTo method of a value-type is called with an argument that is a reference-type.
I've checked that MS System.Boolean, Mono System.Boolean, Mono System.Data.SqlTypes.SqlBoolean and NullableTypes NullabBoolean impementation of IComparable.CompareTo(object value) do work the same way:
the result is
- A positive integer when 'value' is a null reference.
-ArgumentException is thrown if 'value' is not a Boolean.
- A positive integer when 'this' instance is greater than 'value'.
-A negative integer when 'this' instance is less than 'value'.
-Zero when 'this' instance is equal to 'value'.
MSDN documentation for MS System.Data.SqlTypes.SqlBoolean is the same as above anyway the implementation have a different behaveour (it throw an Argument exception when 'value' is a null reference)!
So IMHO there is a bug in the MS SqlBoolean implementation of CompareTo while the documentation is right.
Also IMHO the documented behaveour (MS MSDN) of CompareTo is consistent.
Do you agree?
Bye (luKa)
The thing is that I can't assign a null reference to a ValueType. This leaves me wondering if a null reference can be considered 'of the same type' as some ValueType. Given that I can't assign it the value, there is a pretty good argument that it isn't.
The spec is still ambiguos, given that it doesn't define what to do in this case. Given the ambiguity I could argue that both implementations are valid, or invalid, depending on how I felt.
My gripe is that an interface should be crystal clear, and this one isn't. The fact that it has various interpretations in the framework itself is evidence of this ambiguity.
John.
>
>This leaves me wondering if a null reference
>can be considered 'of the same type' as some
>ValueType.
>
IMHO the answer in no.
Also a null reference cannot be considered of the same type' of a reference type: a null has no type.
Look String.CompareTo implementation: it is a reference type (even if it implements a copy by value semantic) and it return a positive integer for a null argument, it doesn't throw an ArgumentException.
MS SqlTypes, MS types in OracleClient, Oracle DataAccess.Types implementation of CompateTo all throw an ArgumentException for a null argument.
I think this is to avoid confusion between the Null value (ie SqlBoolean.Null that represent the SQL NULL value for a DB Boolean column) and the null reference.
So IMHO .NET Provider types as SqlTypes just have different spec. This is the point!!!
And also... .NET Provider spec give a different meaning to IComparable.CompareTo and this is BAD!!! (You was right about this lamespec.)
Indeed Mono implementation of CompareTo for SqlTypes return a positive integer for a null argument, it doesn't throw an ArgumentException.
So the dispute is between the .NET built-in types end .NET Provider types and not between reference-types and value-types (as you told in the first posting).
In the end... NullableTypes are primarily based on .NET built-in value-types spec so NullableBoolean implementation of CompareTo work like System.Boolean (and every .NET built-in types) and not like SqlBoolean.CompareTo.
bye (luKa)
> So the dispute is between the .NET built-in types end .NET Provider types and not between reference-types and value-types (as you told in the first posting). <
Actually, my gripe remains about the poor definition of the IComparable interface, the rest was just an example to prove my point.
An interface is contract, and contract is crucial to good software.
There are more things that haven't been covered by the IComparable interface that arise from our discussion. For example, what to do when you have a ValueType whose instance represents NULL? Does IComparable deny the existance of nullable types? It shouldn't, particularly if it is intended for use (and used) by nullable types.
> In the end... NullableTypes are primarily based on .NET built-in value-types spec so NullableBoolean implementation of CompareTo work like System.Boolean (and every .NET built-in types) and not like SqlBoolean.CompareTo. <
Sure, but System.Boolean isn't able to represent NULL. So, by definition NullableBoolean.CompareTo can't work like Boolean.CompareTo becuse it can be passed a NULL value. IComparable.CompareTo doesn't define behaviour for this. You said in an earlier post that "the doco for IComparable.CompareTo cannot say that two null references compare equal to each other.", but in fact it can, and it does, even though you are correct in saying that this instance method could never be used to test two null references. It can however be used to test two null values, for example, if I call NullableBoolean.Null.CompareTo(NullableBoolean.Null) then what should I expect? Do I return greater than one? Less than one? Zero? The interface doesn't tell me what to do, I might choose to think about the null that a null reference causes a 'higher' comparison and reutrn 1 because the param represents null. Or I might decide that because this instance is null I should return -1 and not even bother testing my param. Or I could say that both values represent null and return 0, in the same way that two null references are said to behave.
Anyway, it's shit, and I'm pissed off. Imagine having to waste my time having a lame discussion like this about first class interfaces in what is supposed to be the 'worlds leading framework' from one of the 'worlds leading software vendors'.
John.
I thought of another area that is left unaddressed by the IComparable.CompareTo(object) interface method.
What if the argument passed as type System.Object is not of the same type as the instance that exports the IComparable interface but defines either an explicit or implicit conversion for the type of parameter passed. Is it really necessary to throw an ArgumentException in this case (the spec suggests that this is the rule), why not allow for the conversion, implicit conversion seems fair, and there may be a case for allowing explicit conversions too..
John.
..and more!
What if you allow for testing implicit casts, and the cast from a null reference results in a null value, as with SqlBinary for example. If I call SqlBinary.Null.CompareTo(null) since SqlBinary.Null == (SqlBinary)null; should I return 0?
It's a toughie..
John.
> Actually, my gripe remains about the poor definition of the IComparable interface, the rest was just an example to prove my point. <
John, I totally agree!
I would like to have SqlTypes IComparable interface implementation behave like .NET built-in types IComparable implementation (a null reference should not throw an ArgumentException).
>For example, what to do when you have a ValueType whose instance represents NULL? Does IComparable deny the existence of nullable types? It shouldn't, particularly if it is intended for use (and used) by nullable types.<
In the CLR/CTS a SqlBoolean.Null and a NullableBoolean.Null are (singleton) values and they are different to a null reference.
>if I call NullableBoolean.Null.CompareTo(NullableBoolean.Null) then what should I expect? Do I return greater than one? Less than one? Zero? The interface doesn't tell me what to do,<
NullableBoolean.Null is a value with an applicative meaning (not defined by the CLR/CTS that don't know of the existence of NullableBoolean.Null).
The result of the comparison between values in IComparable specs are left free to the implementer (but it must be a strict ordering and it must be coherent with Equals, GetHashCode and operators > <).
For Example In NullableTypes the result of the comparison with a NullableBoolean.Null is based on on SQL specs for NULL [1] and not on IComparable specs.
[1] SQL92 (ISO/IEC 9075:1992, Database Language SQL- July 30, 1992) - http://www.contrib.andrew.cmu.edu/%7Eshadow/sql/sql1992.txt
>What if the argument passed as type System.Object is not of the same type as the instance that exports the IComparable interface but defines either an explicit or implicit conversion for the type of parameter passed.<
This limitation is due to the fact that actually the .NET Framework have interfaces that are not strong typed (have a generic object parameter).
So we cannot overload CompareTo for different parameter types (as is in C++).
We can only have one implementation of CompareTo that must behave based only on the actual type of the argument passed as a System.Object.
The only way to have IComparable consistent with Equals and with GetHashCode is to avoid comparison between different types.
So you can think that C# is less OO then C++ (because you cannot satisfy the Liskov Substitution Principle for CompareTo in C#) but this is a tread off between power and simplicity.
IMHO IComparable.CompareTo would be perfect if SqlTypes will not throw an ArgumentException for a null reference argument.
Bye (luKa)
> In the CLR/CTS a SqlBoolean.Null and a NullableBoolean.Null are (singleton) values and they are different to a null reference. <
This is not actually true. SqlBoolean.Null and NullableBoolean.Null are not actually singletons. They are static readonly instances of a value type. A singleton is a class that guarantees there is only one instance. DBNull for example is a singleton, the only instance of which is accessible via DBNull.Value.
Speaking of DBNull, why isn't there an implicit conversion from DBNull to a Sql<T> or Nullable<T>? I'm surprised at this, and I wonder if there is a good reason. I'm very tempted to add a supported conversion from a DBNull to a Cti<T> (my type library, that supports nullable values among other things, prefixes 'Cti'), but I'm wondering why such a conversion is lacking in other types. Is it a bad idea to define this type of conversion as a part of the type? Why would I always need to defer to a 'Convert' class's static conversion methods rather than allow for something like:
this.FirstName = DBNull.Value;
The thing is that (using my types as an example, because when I say 'nullable types' I don't really mean 'NullableTypes' I mean 'types that support null values') CtiInt32.Null and CtiInt16.Null are not the same value, so is it that a 'null value' (I use the term 'null value' to differentiate between 'null reference', even though I know that in the DB world many consider the term 'null value' misleading and therefore depricated) still confers type information? Is that why I shouldn't allow for conversion from DBNull.Value to say CtiInt32.Null or CtiInt16.Null depending on my target type..? As far as the framework is concerned by modelling a 'null value' as DBNull.Value it doesn't try to infer any type information from a null value, the value is simply missing, even its type can not be known. It's interesting stuff.. I'm very tempted to allow for this implicit conversion, I don't understand a convincing argument against it.
I like the 'uninitialized value type state has null value equality' paradigm, I therefore design my structs to reference an uninitialized value type for the public static readonly Null field too.
I'm glad you mentioned the Liskov Substitution Principle, you are correct in saying that IComparable.CompareTo violates this principle. In my opinion it does this in a terrible way, and this interface is an example of how unsofisticated even our modern programming frameworks are. It is obviously just a hack in the face of the inability to express what is actually desired. If I can not compare a SqlBoolean to anything but a SqlBoolean (according to this interface I can't even compare it to Boolean) then why would I pass the value to be compared as Object? The answer is language limitation, it sucks, but it is really a necessary evil. To make things worse, I can actually allow comparison to 'any reference type' provided the reference is null, in which case I don't throw a type error, but return 1. That's pretty shit, but to make it worse still not all objects are reference types, even though they derive from Object.
The way that I've implemented the IComparable interface on my library is:
public struct MyValue : IComparable {
Int32 IComparable.CompareTo(Object value) {
if (value == null) {
return 1;
}
if (value is MyValue) {
return this.CompareTo((MyValue)value);
}
throw new ArgumentException();
}
public Int32 CompareTo(MyValue value) {
// do actual value comparison here
}
}
I'm pretty happy with this. It allows me to avoid boxing if I call on the MyValue interface, and MyValue doesn't export a bogus interface. Because of the inability to express what I want to allow in C# I still provide the IComparable interface and support it as per the specification (as far as I was able to interpret it, not that I agree with it).
One big challenge that I had when designing my type library was to what extent I would offer support for operating on NULL values. The thing is that I need to be able to represent null, but I don't think I actually need to be able to operate on it extensively. Most of the theory about how to treat null stems from databases where working with sets with a functional language is considered important. My C# code is imperative, and I don't really need for my nullable types to support nullable values in a transparent manner. For example, I don't think that I want to allow for addition of two values that would return a null value where one was unknown. In my imperitive environment, this is likely to lead to confusion, so it is better in my view that I don't support such operations on null values at all. In order to add two values that could be null I have decided to query for their values and if either is null then an exception will be thrown. My justification is that in my imperitive code null must be handled, if it needs to be handled in a set based manner then this will need to be supported without the help of the type. The only operations I allow on a value that can possibly represent null are comparison operations, which can return True, False, and Null. This will leave me with the 'safest' code in my view, because developers won't accidentally end up failing to address a null value that can not be operated on imperatively. For example:
MyValue total;
foreach (MyValue value in this.Values) {
total += value;
}
Console.WriteLine(total);
This could would print 'Null' if any of the values were null. This is a problem, because I'd rather force the programmer to deal with a null value in this environment. Either, all values are not null, in which case this would work, or not, in which case it throws an exception:
Int32 total;
foreach (MyValue value in this.Values) {
total += value.Value;
}
If it is possible that I have a null value, then I need to define behaviour suiting the business requirements, for example:
Int32 total;
Int32 unknown;
foreach (MyValue value in this.Values) {
if (value.IsNull) {
unknown++;
}
else {
total += value.Value;
}
}
or:
Int32 total;
foreach (MyValue value in this.Values) {
if (!value.IsNull) {
total += value.Value;
}
}
or whatever else may be a suitable treatment of null value in this context, given that it is possible that it occurs.
The nullable types still leave a programmer with potential gotchas, because something like this:
if (this.FirstName == value) {
}
else {
// don't assume that this.FirstName != value
}
But, given that I only support limited comparison operations, null will usually be dealt with before operating on a value. I'm even tempted to only support == and != operations, but at the moment I also support >, >=, <, <= ops too. For my nullable boolean value I also support !, & and |, but nothing else. For example, the Xor operator is not defined at all for my nullable boolean type, I can still use the ^ operator on its value, provided it is not null.
Anyway, enough ranting from me.
John.