0

I have the following method:

public static bool IsBetween<T>(this IComparable<T> value, T lowerBound, T upperBound)
    where T : IComparable<T>
{
    Contract.Requires<>(value != null);
    Contract.Requires<>(lowerBound != null);
    Contract.Requires<>(upperBound != null);
    Contract.Requires<>(upperBound.CompareTo(lowerBound) >= 0);

    return IsBetween(value, lowerBound, upperBound, InclusionOptions.None);
}

public static bool IsBetween<T>(this IComparable<T> value, T lowerBound, T upperBound,
     InclusionOptions options) where T : IComparable<T>
{
    Contract.Requires<>(value != null);
    Contract.Requires<>(lowerBound != null);
    Contract.Requires<>(upperBound != null);
    Contract.Requires<>(upperBound.CompareTo(lowerBound) >= 0); //Code Contracts Issue

    ...
}

The problem here is that it does not like my last requirement. It states CodeContracts: requires unproven: upperBound.CompareTo(lowerBound) >= 0. I'm not really sure the proper way to fix this here. I need to make sure that when I do my comparison values that I actually have a real lowerBound and upperBound value and that the lowerBound value is not above the upperBound value.

Oh, and I can't use the actual < or > operators because you can't apply them to the type 'T'.


Lastly, I know this can be a separate question, but it's highly related... if someone knows why I still get CA1062 Code Analysis errors when I'm using Code Contracts v1.4.50126.1 please tell me what to do to fix it: CA1062: Microsoft.Design : In externally visible method 'MyClass.IsBetween<T>(this IComparable<T>, T, T), validate parameter 'upperBound' before using it.

myermian
  • 31,823
  • 24
  • 123
  • 215
  • I haven’t used `Contract`, but wouldn’t the warning be relevant to the code which is _calling_ your method, rather than the method definition itself? Do you have any calls in your application where you’re violating the condition? – Douglas Feb 25 '12 at 16:47
  • Nope. This in my utility class. No code in that class calls this member. It is 100% isolated. – myermian Feb 25 '12 at 16:53
  • I'd recommend removing the CA1062 analysis. It is currently incompatible with Code Contracts, and Code Contracts will warn you anyway if you have potential issues. – porges Feb 27 '12 at 23:50
  • @m-y: *something* has to be calling that code, or Code Contracts wouldn't generate the warning. The warning means that the calling code does not prove that the requirements are correct. – porges Feb 27 '12 at 23:52
  • @Porges: You're right, I didn't realize that I had a method named `IsBetween` that had an additional parameter calling into this method. But, it still doesn't explain why I can't use `Requires` and/or `Result` with `IComparable`/`IComparable`. They're simply method calls which return true/false. – myermian Mar 10 '12 at 21:13
  • @m-y: can you post the calling code as well? – porges Mar 13 '12 at 23:35
  • @Porges: Done... I think I might know of a solution to this, which is to not use `IComparable` or `IComparable` in my requirements and allow the lowerBound/upperBound values to be passed in either order, then calculate which should be the upperBound/lowerBound from within because one has to be an upperBound in comparison to the other (even if they are equal). – myermian Mar 14 '12 at 11:04
  • @m-y: this looks like a bug in CC. The Requires on the calling method should be proving this, but it isn't. As an aside, I'd change the first parameter from `IComparable` to `T`. At the moment, the parameter will have to be boxed, which will impact performance, especially since this method will be used with primitive values 99% of the time. I've reported this as a bug on the CC forum: http://social.msdn.microsoft.com/Forums/en-NZ/codecontracts/thread/7ed5f4af-df26-4326-ab7d-7f2512e7fbf6 – porges Mar 14 '12 at 11:11
  • @Porges: I left it as `IComparable` because LINQ's extension methods use `IEnumerable` as the parameter, so I figured that's the pattern to follow. – myermian Mar 14 '12 at 11:18

2 Answers2

0

The problem with your method is that, when you call upperBound.CompareTo, you are assuming that the compiler knows that type T (which is the declared type of the upperBound parameter) implements the IComparable<T> interface (which declares the CompareTo method).

It is typically always the case (by convention) that an IComparable<T> interface would be implemented by the type T (if at all); however, the compiler does not know it unless you explicitly specify it through a type constraint.

public static bool IsBetween<T>(this T value, T lowerBound, T upperBound)
    where T : IComparable<T>
{
    Contract.Requires(value != null);
    Contract.Requires(lowerBound != null);
    Contract.Requires(upperBound != null);
    Contract.Requires(upperBound.CompareTo(lowerBound) >= 0);

    // ...
}
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • Oh, sorry... in my haste I forgot to put down that I do have a type constraint... but I don't see that having anything to do with the line that has an issue. – myermian Feb 25 '12 at 16:33
  • The line would fail to compile if you don’t add the type constraint. – Douglas Feb 25 '12 at 16:41
  • Douglas: I do have that line (hence it compiles). In my haste to copy my code over and format it to look well in Stackoverflow deleted the constraint line. I do have a generic constraint. I have updated my question to reflect my method accurately. – myermian Feb 25 '12 at 16:47
  • @m-y: Added comment beneath your question. – Douglas Feb 25 '12 at 16:48
0

I'll use int as an example.

The contract for int.CompareTo(int) does not ensure any particular return value. You know that if a >= b, then a.CompareTo(b) >= 0, but since this has not been expressed as a contract, the only thing the static checker sees is "a.CompareTo(b) returns an int". "An int" cannot be proven to be nonnegative.

You should be able to add something like

Contract.Assert(a >= b);
Contract.Assume(a.CompareTo(b) >= 0);

in the places you call your function. This lets the static checker first attempt to prove that a >= b and notify you if it cannot prove that, and then trust you that the function requirement is met.

If you often call this function for an int, it may be worthwhile to create a wrapper function with this modified int-specific contract:

public static bool IsBetween(this int value, int lowerBound, int upperBound)
{
    Contract.Requires<>(value != null);
    Contract.Requires<>(lowerBound != null);
    Contract.Requires<>(upperBound != null);
    Contract.Requires<>(upperBound >= lowerBound);
    Contract.Assume(upperBound.CompareTo(lowerBound) >= 0);
    return IsBetween<int>(value, lowerBound, upperBound);
}

and similarly for other types.