Click here to Skip to main content
15,885,914 members
Articles / Programming Languages / C#
Tip/Trick

Why C# interface inheritance makes sense: see LINQ...

Rate me:
Please Sign up or sign in to vote.
4.00/5 (3 votes)
22 May 2012CPOL3 min read 34.3K   7   10
How Linq extension methods benefit from interfaces inheriting from interfaces

Introduction

I came across the debate[^] if the following code is good or bad:

C#
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:

C#
// Linq-to-object
int i = myList.Count(r=>r.Value==0);
// Linq-to-SQL
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

C#
public interface IQu /*: IEn*/ { }
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)'
C#
// Stands for System.Collections.Generic.IEnumerable<T>.
public interface IEn
{
    // Stands for the IEnumerable<T> interface function: GetEnumerator().
    int Count { get; } 
}
// Stands for System.Linq.IQueryable<T>: no interface functions.
// This exists only as distinct interface to IEnumerable<T>.
public interface IQu : IEn
{
}
// Stands for any C# collection or array like int[], IList<string>, etc.
public class CsArr : IEn
{
    // construction
    public CsArr(int size) { Count = size; }
    // implement interface
    public int Count { get; private set; }
}
// Stands for the System.Data.Linq.Table class (used as db table in linq-to-SQL).
public class DbTab : IQu, IEn
{
    // constructor
    public DbTab(int size) { Count = size; }
    // implement interface
    public int Count { get; set; }
}
// Stands for System.Linq.Enumerable extension class.
public static class EnExt
{
    // Stands for any Linq-to-object extension method. All these methods directly call the delegate.
    public static void ExtCall(this IEn e, Func<int, int> del)
    { Console.WriteLine("IEn: delegate({0}) = {1}", e.Count, del(e.Count)); }
}
// Stands for System.Linq.Queryable extension class.
public static class QuExt
{
    // Stands for any Linq-to-SQL (or Linq-to-QureyProvider) extension method.
    // All these methods are overloads to some of the Enumerable methods.
    // The Expression<...> arguments allow to translate the expression of the delegate into SQL.
    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); // Stands for a C# array or collection.
        var t = new DbTab(20); // Stands for a Linq db table.
        a.ExtCall(v=>v*v);     // both calls (direct delegate or Expression)...
        t.ExtCall(v=>v*v);     // ...are identical on client side
    }
}

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:

  1. from x = v * v;
  2. over Func<int, int> f = (i) => i * i; x = f(v);
  3. 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder eXternSoft GmbH
Switzerland Switzerland
I feel comfortable on a variety of systems (UNIX, Windows, cross-compiled embedded systems, etc.) in a variety of languages, environments, and tools.
I have a particular affinity to computer language analysis, testing, as well as quality management.

More information about what I do for a living can be found at my LinkedIn Profile and on my company's web page (German only).

Comments and Discussions

 
Questionhow sequence in inheritance decides what to call Pin
raty4-Oct-12 12:11
raty4-Oct-12 12:11 
AnswerRe: how sequence in inheritance decides what to call Pin
Andreas Gieriet4-Oct-12 21:12
professionalAndreas Gieriet4-Oct-12 21:12 
GeneralFood for thought Pin
PIEBALDconsult22-May-12 17:50
mvePIEBALDconsult22-May-12 17:50 
GeneralRe: Food for thought Pin
Andreas Gieriet23-May-12 11:31
professionalAndreas Gieriet23-May-12 11:31 
GeneralRe: Food for thought Pin
PIEBALDconsult24-May-12 9:28
mvePIEBALDconsult24-May-12 9:28 
Andreas Gieriet wrote:
I consider it correct behaviour


I don't -- it's not consistent. And it actually varies by the value of the parameter, not just the datatype.
GeneralRe: Food for thought Pin
Andreas Gieriet24-May-12 22:20
professionalAndreas Gieriet24-May-12 22:20 
GeneralRe: Food for thought Pin
PIEBALDconsult25-May-12 3:01
mvePIEBALDconsult25-May-12 3:01 
GeneralRe: Food for thought Pin
Andreas Gieriet25-May-12 11:39
professionalAndreas Gieriet25-May-12 11:39 
GeneralRe: Food for thought Pin
PIEBALDconsult25-May-12 15:11
mvePIEBALDconsult25-May-12 15:11 
GeneralRe: Food for thought Pin
Andreas Gieriet29-May-12 19:53
professionalAndreas Gieriet29-May-12 19:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.