Nullable generic type used with IComparable. Is it possible?
Asked Answered
J

2

12

I'm trying to create a simple Clamp (so that I can bound the values of anything comparable ... mostly for number types such as int, double, etc.)

The problem is if I do the following I get an error, but according to MSDN IComparable's CompareTo is supposed to be able to handle null values.
Quote: "By definition, any object compares greater than null, and two null references compare equal to each other."

public static T Clamp<T>(this T value, T min, T max)
    where T : IComparable<T>
{
    if (value.CompareTo(max) > 0)
        return max;

    if (value.CompareTo(min) < 0)
        return min;

    return value;
}



private Int32? _zip;
public Int32? Zip
{
    get
    {
        return _zip;
    }
    set
    {
        _zip = value.Clamp<Int32?>(0, 99999);
    }
}
Jenjena answered 20/7, 2010 at 21:37 Comment(0)
G
7

Remember, Int32? is a shorthand for Nullable<Int32>. Since Nullable<T> does not implement IComparable<T>, your code, as structured, won't compile.

You can, however, overload the method:

public static T? Clamp<T>(this T? value, T? min, T? max) 
    where T : struct, IComparable<T> 
{ 
    // your logic...
} 

Of course, if you're planning on working with nullable types, you have to define how you will clamp null values...

If you don't actually need to clamp null values, it may be simpler to just first check for null in your property getter:

public Int32? Zip
{
   ...
   set
   {
       _zip = value == null ? value : value.Value.Clamp<Int32>(0,99999);
   }

Or better yet, make it part of the implementation of the additional overload to Clamp...

Gamber answered 20/7, 2010 at 21:41 Comment(1)
I'm not sure why I didn't just do (value == null ) ? value : value.Clamp<Int32>(0, 99999); to begin with. I guess I was just trying to force the Clamp to do it automatically. But yes, it actually makes more sense to not null it since it is clamping.Jenjena
C
16

As said by @LBushkin Nullable< T > or T? does not implement IComparable interface. The given solution is ok, however I prefer to have the nullable comparing logic inside a specialized class in that matter, following the Single Responsibility Principle, and also than can be used for comparing any Nullable types.

For example, you could create a generic Nullable type comparer class like this:

public class NullableComparer<T> : IComparer<Nullable<T>>
      where T : struct, IComparable<T>
{

     public int Compare(Nullable<T> x, Nullable<T> y)
     {
        //Compare nulls acording MSDN specification

        //Two nulls are equal
        if (!x.HasValue && !y.HasValue)
            return 0;

        //Any object is greater than null
        if (x.HasValue && !y.HasValue) 
            return 1;

        if (y.HasValue && !x.HasValue)
            return -1;

        //Otherwise compare the two values
        return x.Value.CompareTo(y.Value);
     }

}

In this case you would use this class like this:

public static T? Clamp<T>(this T? value, T? min, T? max)
    where T : struct
{
    var comparer = new NullableComparer<T>();

    if (comparer.Compare(value, max) > 0)
        return max;

    if (comparer.Compare(value, min) < 0)
        return min;

    return value;
}

Handy for saving in your helpers library.

Hope it helps!

Crispin answered 20/7, 2010 at 22:9 Comment(0)
G
7

Remember, Int32? is a shorthand for Nullable<Int32>. Since Nullable<T> does not implement IComparable<T>, your code, as structured, won't compile.

You can, however, overload the method:

public static T? Clamp<T>(this T? value, T? min, T? max) 
    where T : struct, IComparable<T> 
{ 
    // your logic...
} 

Of course, if you're planning on working with nullable types, you have to define how you will clamp null values...

If you don't actually need to clamp null values, it may be simpler to just first check for null in your property getter:

public Int32? Zip
{
   ...
   set
   {
       _zip = value == null ? value : value.Value.Clamp<Int32>(0,99999);
   }

Or better yet, make it part of the implementation of the additional overload to Clamp...

Gamber answered 20/7, 2010 at 21:41 Comment(1)
I'm not sure why I didn't just do (value == null ) ? value : value.Clamp<Int32>(0, 99999); to begin with. I guess I was just trying to force the Clamp to do it automatically. But yes, it actually makes more sense to not null it since it is clamping.Jenjena

© 2022 - 2024 — McMap. All rights reserved.