Click here to Skip to main content
Click here to Skip to main content

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

, 22 May 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
How Linq extension methods benefit from interfaces inheriting from interfaces

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:

// 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

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)'
// 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)

Share

About the Author

Andreas Gieriet
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).
Follow on   LinkedIn

Comments and Discussions

 
Questionhow sequence in inheritance decides what to call Pinmemberraty4-Oct-12 13:11 
AnswerRe: how sequence in inheritance decides what to call PinmemberAndreas Gieriet4-Oct-12 22:12 
GeneralFood for thought PinmemberPIEBALDconsult22-May-12 18:50 
GeneralRe: Food for thought PinmemberAndreas Gieriet23-May-12 12:31 
GeneralRe: Food for thought PinmemberPIEBALDconsult24-May-12 10:28 
GeneralRe: Food for thought PinmemberAndreas Gieriet24-May-12 23:20 
GeneralRe: Food for thought PinmemberPIEBALDconsult25-May-12 4:01 
GeneralRe: Food for thought PinmemberAndreas Gieriet25-May-12 12:39 
GeneralRe: Food for thought PinmemberPIEBALDconsult25-May-12 16:11 
GeneralRe: Food for thought PinmemberAndreas Gieriet29-May-12 20:53 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 22 May 2012
Article Copyright 2012 by Andreas Gieriet
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid