Monday, February 7, 2011

Extension Methods not working for an interface

Inspired by the MVC storefront the latest project I'm working on is using extension methods on IQueryable to filter results.

I have this interface;

IPrimaryKey
{
  int ID { get; }
}

and I have this extension method

public static IPrimaryKey GetByID(this IQueryable<IPrimaryKey> source, int id)
{
    return source(obj => obj.ID == id);
}

Let's say I have a class, SimpleObj which implements IPrimaryKey. When I have an IQueryable of SimpleObj the GetByID method doesn't exist, unless I explicitally cast as an IQueryable of IPrimaryKey, which is less than ideal.

Am I missing something here?

  • This cannot work due to the fact that generics don't have the ability to follow inheritance patterns. ie. IQueryable<SimpleObj> is not in the inheritance tree of IQueryable<IPrimaryKey>

  • Edit: Konrad's solution is better because its far simpler. The below solution works but is only required in situations similar to ObjectDataSource where a method of a class is retrieved through reflection without walking up the inheritance hierarchy. Obviously that's not happening here.

    This is possible, I've had to implement a similar pattern when I designed a custom entity framework solution for working with ObjectDataSource:

    public interface IPrimaryKey<T> where T : IPrimaryKey<T>
    {
        int Id { get; }
    }
    
    public static class IPrimaryKeyTExtension
    {
         public static IPrimaryKey<T> GetById<T>(this IQueryable<T> source, int id) where T : IPrimaryKey<T>
         {
             return source.Where(pk => pk.Id == id).SingleOrDefault();
         }
    }
    
    public class Person : IPrimaryKey<Person>
    {
        public int Id { get; set; }
    }
    

    Snippet of use:

    var people = new List<Person>
    {
        new Person { Id = 1 },
        new Person { Id = 2 },
        new Person { Id = 3 }
    };
    
    var personOne = people.AsQueryable().GetById(1);
    
    From cfeduke
  • It works, when done right. cfeduke's solution works. However, you don't have to make the IPrimaryKey interface generic, in fact, you don't have to change your original definition at all:

    public static IPrimaryKey GetByID<T>(this IQueryable<T> source, int id) where T : IPrimaryKey
    {
        return source(obj => obj.ID == id);
    }
    
    Kirschstein : Excellent - I've unaccepted the original answer. I shall try this tomorrow. Thanks both of you.
    Garry Shutler : I'd just like to say that my answer was not wrong, it was the reason his code was not working, I just didn't take the time to work out a solution. Up voted your better answer.
    Kirschstein : Just a very minor point: it should be GetByID(this IQueryable source...

0 comments:

Post a Comment