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.