1

I'm not really sure if there is any real difference here in the two signatures:

public static class MyCustomExtensions
{
    public static bool IsFoo(this IComparable<T> value, T other)
        where T : IComparable<T>
    {
        // ...
    }

    public static bool IsFoo(this T value, T other)
        where T : IComparable<T>
    {
        // ...
    }
}

I think these will essentially operate almost identically, but I'm not quite sure... what am I overlooking here?

michael
  • 14,844
  • 28
  • 89
  • 177
  • What is `T` in this example, and when you say "extension method", what do you mean? – Lasse V. Karlsen May 08 '13 at 21:49
  • 1
    If `T` is a value type, like `int` (`System.Int32`), in the first example `value` (bot not `other`) will be __boxed__. In the second method, `value` is not boxed if `T` is a value type. ADDITION: Also, of course, in the first example, in theory, some type other than `T` could implement `IComparable`. Note that `IComparable<>` is __contravariant__ in its type argument ... – Jeppe Stig Nielsen May 08 '13 at 21:49
  • 1
    Make sure you write real code in your question. You have not declared T, though I assume you mean that is a generic argument to the two IsFoo methods. Also, you have not used the `this` keyword for the first parameter to each method, though I assume you meant to since you say "extension method". – Lasse V. Karlsen May 08 '13 at 21:56
  • @LasseV.Karlsen: Yep, extension method... fixed. – michael May 08 '13 at 21:59
  • 1
    You should add `` to your method declarations as well, otherwise the question still stands: what is `T`? – Lasse V. Karlsen May 08 '13 at 22:04

4 Answers4

6

Yes there is.

The first signature would match any type that can be compared to T, not just T values. So any type that implements IComparable<int> can be used by the first signature, not just int.

Example:

void Main()
{
    10.IsFoo(20).Dump();
    new Dummy().IsFoo(20).Dump();

    IComparable<int> x = 10;
    x.IsFoo(20).Dump();

    IComparable<int> y = new Dummy();
    y.IsFoo(20).Dump();
}

public class Dummy : IComparable<int>
{
    public int CompareTo(int other)
    {
        return 0;
    }
}

public static class Extensions
{
    public static bool IsFoo<T>(this IComparable<T> value, T other)
        where T : IComparable<T>
    {
        Debug.WriteLine("1");
        return false;
    }

    public static bool IsFoo<T>(this T value, T other)
        where T : IComparable<T>
    {
        Debug.WriteLine("2");
        return false;
    }
}

Will output:

2
False
1
False
1
False
1
False

I tested this with LINQPad.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • So, if I only had the second method, it would be impossible to use this extension method whe comparing to an int, even though the class implements `IComparable`? – michael May 08 '13 at 21:58
  • Yes, because since you're comparing to an int, T must be int, and thus you must be comparing an int to an int, since both parameters are of type T. The first method can be used with anything that can be compared to an int (through `IComparable`). – Lasse V. Karlsen May 08 '13 at 21:59
  • In the edited answer, you will notice that the last two calls will end up the same, in this case it's about what the compiler knows. Since it doesn't realize the value you're comparing is an int, since you're passing it a `IComparable`, it will use the first signature. – Lasse V. Karlsen May 08 '13 at 22:04
0

If we rewrite it slightly to use IList instead of IComparable, wouldn't that be the same question?

In that case it is clear to see that IsFoo1 is completely different to IsFoo2.
Because IsFoo1 accepts first argument of essentially IList<IList<T>>
whereas IsFoo2 accepts first argument of just IList<T>

public static class MyCustomExtensions
{
    public static bool IsFoo1(IList<T> value, T other)
        where T : IList<T>
    {
        // ...
    }

    public static bool IsFoo2(T value, T other)
        where T : IList<T>
    {
        // ...
    }
}

So no they are not the same at all.

demoncodemonkey
  • 11,730
  • 10
  • 61
  • 103
0

They aren't identical. In the first one you are passing in IComparable<T> to the first but not the second, so your actual types would be <IComparable<IComparable<T>> and IComparable<T>.

EDITED based on Lee's feedback: these below look identical, but while both require value and other to implement IComparable, the second also requires that they are assignable to T.

public static bool IsFoo<T>(IComparable<T> value, IComparable<T> other)
{
    // ...
}

public static bool IsFoo<T>(T value, T other)
    where T : IComparable<T>
{
    // ...
}
gabnaim
  • 1,103
  • 9
  • 13
  • They aren't identical - your second requires `value` and `other` be assignable to the same type while the first only requires they both implement `IComparable` for the same type `T`. – Lee May 08 '13 at 22:07
0

The difference is pretty obvious. Note that you have to define T either in the method (generic method) or in a containing class (generic class, not possible with extension methods). Below I call the two methods 1 and 2:

public static bool IsFoo1<T>(this IComparable<T> value, T other)
    where T : IComparable<T>
{
    return true;
}

public static bool IsFoo2<T>(this T value, T other)
    where T : IComparable<T>
{
    return true;
}

There are differences depending on whether T is a value type or a reference type. You can restrict to either by using constraint where T : struct, IComparable<T> or where T : class, IComparable<T>.

Generally with any type T: Some crazy type X might be declared IComparable<Y> where Y is distinct (and unrelated) to X.

With value types:

With IFoo1 the first parameter value will be boxed, whereas value in IFoo2 will not be boxed. Value types are sealed, and contravariance does not apply to value types, so this is the most important difference in this case.

With reference types:

With reference type T, boxing is not an issue. But note that IComparable<> is contravariant ("in") in its type argument. This is important if some non-sealed class implements IComparable<>. I used these two classes:

class C : IComparable<C>
{
    public int CompareTo(C other)
    {
        return 0;
    }
}
class D : C
{
}

With them, the following calls are possible, some of them because of inheritance and/or contravariance:

        // IsFoo1

        new C().IsFoo1<C>(new C());
        new C().IsFoo1<C>(new D());
        new D().IsFoo1<C>(new C());
        new D().IsFoo1<C>(new D());

        new C().IsFoo1<D>(new D());
        new D().IsFoo1<D>(new D());

        // IsFoo2

        new C().IsFoo2<C>(new C());
        new C().IsFoo2<C>(new D());
        new D().IsFoo2<C>(new C());
        new D().IsFoo2<C>(new D());

        //new C().IsFoo2<D>(new D()); // ILLEGAL
        new D().IsFoo2<D>(new D());

Of course, in many cases the generic argument <C> can be left out because it will be inferred, but I included it here for clarity.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181