Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Double" doesn't get cast as "IComparable<Double>"

I have created a template that returns the index of the max value in an array. It works, but only if I pass in a weirdly cast List.

    static public int FindMaxIndex<T>( IEnumerable<IComparable<T>> arr )
    {
        IEnumerator<IComparable<T>> it = arr.GetEnumerator();
        if (!it.MoveNext()) return -1;
        int index = 1, maxIndex = 0;
        IComparable<T> max = it.Current;
        while (it.MoveNext())
        {
            if (max.CompareTo( (T)(it.Current) ) < 0)
            {
                maxIndex = index;
                max = it.Current;
            }
            ++index;
        }
        return maxIndex;
    }

Now to use it:

    List<IComparable<Double>> arr = new List<IComparable<Double>>(); // THIS WORKS
    List<Double> arr = new List<Double>(); // THIS DOESN'T

The later list, which is what I would like to use, gives this compiler error:

cannot convert from 'System.Collections.Generic.List<double>' to 'System.Collections.Generic.IEnumerable<System.IComparable<double>>'

How can this be? "Double" IS an IComparable; taken from its definition:

public struct Double : IComparable, IFormattable, IConvertible, IComparable<Double>, IEquatable<Double>

like image 489
Bill Kotsias Avatar asked Feb 04 '23 11:02

Bill Kotsias


2 Answers

I think other answers have addressed why your code as written isn't working the way you'd expect and even why the code will fail in some cases. However, none of the answers show you the way to do what you want:

public static int FindMaxIndex<T>( IEnumerable<T> source ) where T : IComparable<T>
{
    using( var e = source.GetEnumerator() )
    {
        if( !e.MoveNext() ) return -1;

        T maxValue = e.Current;
        int maxIndex = 0;

        for( int i = 1; e.MoveNext(); ++i )
        {
            if( maxValue.CompareTo( e.Current ) < 0 )
            {
                maxIndex = i;
                maxValue = e.Current;
            }
        }

        return maxIndex;
    }
}

So I've introduced a generic constraint here (the where T : IComparable<T>). That tells the compiler that whatever T happens to be, it will implement IComparable<T>.

Also, I've put the enumerator in a using statement which will guarantee that its Dispose method is called.

Anyway, now when you call this method it will work directly on IEnumerable<double> and even deduce the type parameter for you:

var someDoubles = new List<double> { 3, 2, 1 };
Console.WriteLine( FindMaxIndex( someDoubles ) ) // Prints "0";

Also, if FindMaxIndex is declared in a static class, you can put the this keyword in front of the source parameter to make it an extension method:

public static int FindMaxIndex<T>( this IEnumerable<T> source ) where T : IComparable<T>
{
    // ...
}

Now you'll be able to call it like this:

list.FindMaxIndex()
like image 70
Kyle Avatar answered Feb 07 '23 01:02

Kyle


Generic covariance is only valid when the generic argument is a reference type. Because you have a value type as the argument, it cannot perform any covariant conversions.

like image 43
Servy Avatar answered Feb 07 '23 00:02

Servy