Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
22 May 2012 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here