Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type inference problem when writing a generic extension method with more than one type

I am writing a generic extension method for IEnumerable for mapping a list of objects to another list of mapped objects. This is how I would like the method to work:

IList<Article> articles = GetArticles();
return articles.Map<ArticleViewModel>(_mappingEngine);

This is the method:

public static IEnumerable<T2> Map<T1, T2>(this IEnumerable<T1> list, IMappingEngine engine)
{
    return list.Select(engine.Map<T1, T2>);
}

However articles.Map<ArticleViewModel>(_mappingEngine); gives a compile error. The problem is that the type inference for T1 doesn't work. I have to explicitly call it like this instead:

articles.Map<Article, ArticleViewModel>(_mappingEngine);

If I create an extension method with only one parameter T1 like this:

public static IEnumerable<T1> DummyMap<T1>(this IEnumerable<T1> list, IMappingEngine engine)
{
    return list;
}

Then I can call it like this, without having to specify T1:

articles.DummyMap(_mappingEngine);

Is there are reason why the compiler can't infer the type of T1 in the extension method for Map?

like image 422
Can Gencer Avatar asked Mar 09 '11 08:03

Can Gencer


1 Answers

The problem is that the type inference for T1 doesn't work

Actually, the problem isn't T1 - it is T2; return types are not used in this inference. So you can't do that. One option might be a fluent API, for example:

return articles.Map(_mappingEngine).To<SomeT2>();

with something like:

public static MapProjection<T1> Map<T1>(this IEnumerable<T1> list, IMappingEngine engine)
{
    return new MapProjection<T1>(list, engine);
}
public class MapProjection<T1>
{
    private readonly IEnumerable<T1> list;
    private readonly IMappingEngine engine;
    internal MapProjection( IEnumerable<T1> list, IMappingEngine engine)
    {this.list = list; this.engine = engine;}

    public IEnumerable<T2> To<T2>()
    {
        return list.Select(engine.Map<T1, T2>());
    }
}

assuming that the interface is something like:

public interface IMappingEngine {
    Func<T1, T2> Map<T1, T2>();
}
like image 137
Marc Gravell Avatar answered Sep 27 '22 22:09

Marc Gravell