21

Suppose I have an unconstrained generic method that works on all types supporting equality. It performs pairwise equality checks and so works in O(n2):

public static int CountDuplicates<T>(IList<T> list) 
{
    /* ... */ 
}

I also have a constrained generic method that only works with types supporting sorting. It starts from sorting the list in O(n log n), and then counts all duplicates in one pass:

public static int CountDuplicatesFast<T>(IList<T> list) 
    where T : IComparable<T> 
{
    /* ... */ 
}

So, a caller can choose to invoke the fast method if it is statically known that the type of elements of the list supports ordering. It might happen that the caller itself works with generic IList<T> where T is unconstrained, so its only option to invoke the first (slow) method.

Now, I want the first method to check at runtime if the type T actually implements the interface IComparable<T> and if so, invoke the fast method:

public static int CountDuplicates<T>(IList<T> list)
{
    if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
    {
        return CountDuplicatesFast(list);
    }
    else
    {
        /* use the slow algorithm */
    }
}

The problem is the compiler rejects the invocation CountDuplicatesFast(list):

error CS0314: The type 'T' cannot be used as type parameter 'T' in the generic type or method 'Program.CountDuplicatesFast<T>(System.Collections.Generic.IList<T>)'. There is no boxing conversion or type parameter conversion from 'T' to 'System.IComparable<T>'.

Is it possible to persuade the compiler to trust me that I know what I am doing, and to skip the constraint check?

Piotr Shatalin
  • 443
  • 3
  • 8
  • 1
    Have you tried using Cast? `return CountDuplicatesFast(list.Cast>().ToList());` – Nevyn May 06 '13 at 19:31
  • @Nevyn that produces "The type 'System.IComparable' cannot be used as type parameter 'T' in the generic type or method 'UserQuery.MyType.CountDuplicatesFast(System.Collections.Generic.IList)'. There is no implicit reference conversion from 'System.IComparable' to 'System.IComparable>'." – Tim S. May 06 '13 at 19:36
  • Interesting. Almost the right track, but entirely the wrong execution. I suppose I would need to actually write out a test program and try a few different things. I think cast might be able to do it...but I have no idea how exactly...oh well, for a first guess it wasn't too far off :-) – Nevyn May 06 '13 at 19:40

2 Answers2

8

You can use a helper class and dynamic type to skip compile-time checks:

sealed class CountDuplicatesFastCaller
{
    public int Call<T>(IList<T> list) where T : IComparable<T>
    {
        return CountDuplicatesFast(list);
    }
}

public static int CountDuplicates<T>(IList<T> list)
{
    if (typeof (IComparable<T>).IsAssignableFrom(typeof (T)))
    {
        return ((dynamic) new CountDuplicatesFastCaller()).Call(list);
    }
    else
    {
        /* use the slow algorithm */
    }
}

This should be faster than pure reflection because of DLR caching mechanisms.

Vladimir Reshetnikov
  • 11,750
  • 4
  • 30
  • 51
7

Here's a way to do it using dynamic:

if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
{
    return CountDuplicatesFast((dynamic)list);
}

Or with reflection:

if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
{
    var method = typeof(MyType).GetMethod("CountDuplicatesFast");
    var generic = method.MakeGenericMethod(typeof(T));
    return (int)generic.Invoke(null, new object[] { list });
}

I don't think that there's a way to do this statically (i.e. without reflection or dynamic).

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • 3
    Nice! I did not think about casting the argument. I think my approach is needed only in rare cases when there is a type parameter but no value parameters. – Vladimir Reshetnikov May 06 '13 at 19:39
  • @VladimirReshetnikov hm..how would that help, even then? Unless the helper class added a dummy parameter to let the type be inferred, I don't see it. – Tim S. May 06 '13 at 21:57