Introduction
I came across the debate[^] if the following code is good or bad:
public interface A { ... }
public interface B : A { ... }
This tip does not talk about the question if good or bad. I'm rather interested where that capability of C# has its unique value.
I was contemplating lately how Linq make the distinction between Linq-to-object and Linq-to-SQL. And really, that's where the interface inheritance plays its role: Linq as provided today would not be possible without.
The question
What makes the difference of the follwing two snippets:
int i = myList.Count(r=>r.Value==0);
int j = myDbTable.Count(r=>r.Value==0);
I.e. how does C# decide that the first method is
Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
and the second is
Count<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
Easy answer
Easy enough to make the decision, right?
- The C# arrays and collections implement the
IEnumerable<T>
interface, so the respective extension method for IEnumerable<T>
is taken.
- The dbTable which is of type
System.Data.Linq.Table<T>
and which implements IQueryable<T>
mandates the use of the respective extension method for IQueryable<T>
.
Not so easy answer
But, wait! System.Data.Linq.Table<T>
also implements IEnumerable<T>
. Em, why does the C# compiler does not take the extension method for IEnumerable<T>
?
How does the compiler make the right decision?
- Is the sequence of the interfaces in the Table<T> class relevant?
- Or is any other magic involved?
No, the sequence is not relevant.
The "magic" that is involved - you guess it - is that the IQueryable<T>
interface inherits from IEnumerable<T>
interface. This tells the compiler the the Table is "closer related" to IQueryable<T>
than to IEnumerable<T>
. Thus, the extension method for IQueryable<T>
matches better.
Try it out in a sandbox!
For the purpose of this tip I've re-built very simplified interfaces and classes that show the basic relations of the relevant Linq classes.
You may play around with these and e.g. try to swap the interface sequence in DbTab class: no impact on the result.
Or you may replace the definition of IQu
by
public interface IQu { }
I.e.
IQu
does not inherit any more from
IEn
: the result is a compilation error:
The call is ambiguous between the following methods or properties: 'QuExt.ExtCall(IQu)' and 'EnExt.ExtCall(IEn)'
public interface IEn
{
int Count { get; }
}
public interface IQu : IEn
{
}
public class CsArr : IEn
{
public CsArr(int size) { Count = size; }
public int Count { get; private set; }
}
public class DbTab : IQu, IEn
{
public DbTab(int size) { Count = size; }
public int Count { get; set; }
}
public static class EnExt
{
public static void ExtCall(this IEn e, Func<int, int> del)
{ Console.WriteLine("IEn: delegate({0}) = {1}", e.Count, del(e.Count)); }
}
public static class QuExt
{
public static void ExtCall(this IQu e, Expression<Func<int, int>> del)
{
Console.WriteLine("IQu: Expression({0}): {1} => {2}", del, e.Count, del.Compile()(e.Count));
}
}
public class Program
{
public static void Main(string[] args)
{
var a = new CsArr(10); var t = new DbTab(20); a.ExtCall(v=>v*v); t.ExtCall(v=>v*v); }
}
The output is:
IEn: delegate(10) = 100
IQu: Expression(v => (v * v)): 20 => 400
Conclusion
This tip shows that inheritance of interfaces have a purpose in finding the best match in extension method overloads. This is obvious in the way Linq is implemented.
This mechanism also makes sure that the best match on any overload (extension method or not) is taken.
I find it amazing that this simple feature enables the current Linq implementation (no matter whether design purists say that interfaces inheriting from interfaces is bad...).
And these Linq.Expression parameters are really great too - you get the actually passed expression that enables in the context of Linq to transform into SQL.
Side note: you pay a performance penalty with each level of abstraction:
- from
x = v * v;
- over
Func<int, int> f = (i) => i * i; x = f(v);
- to
Expression<Func<int, int>> e = (i) => i * i; x = e.Compile()(v);
Where to go from here
History
V1.0, 2012-05-22, initial version
V1.1, 2012-05-22, fix some typo, add more elaborate example
V1.2, 2012-05-22, re-shuffled content, enhanced intro and sections, added references
V1.3, 2012-05-23, code got screwed up - restored