Search code examples
c#asp.netgenericshtml-helperasp.net-mvc-5.2

Why type argument cannot be inferred from usage if type argument constraint specifies the type?


I've created an extension method which is supposed to provide table header text for a table containing a collection of items of type IList<T>. Though, the compiler says that TModel cannot be inferred from usage, however, for me it is obvious if the type argument constraint says that TListModel is an IList<TModel>, then TModel could indeed be inferred.

public static MvcHtmlString HeaderFor<TListModel, TModel, TValue>(this HtmlHelper<TListModel> listViewModel, Expression<Func<TModel, TValue>> expression) 
    where TListModel : IList<TModel> 

TListModel is say List<Product>, so TModel is Product, and as such, I'd like to use the HtmlHelper like this:

<th scope="col" class="azonosito">
     @Html.HeaderFor(x => x.Price)
</th>

I now have to use it like this, which is so awkward:

<th scope="col" class="azonosito">
     @Html.HeaderFor((Product x) => x.Price)
</th>

Solution

  • This is a request previously made to the C# language team: REQUEST: Pattern matching/better type inferencing with generics. #5023. However, note the linked comment, this introduces breaking changes, so is not currently implemented. See also https://github.com/dotnet/roslyn/issues/11242 .

    The problem is that the compiler is not able to infer the type of TListModel can only be IList<TModel>. It doesn't understand there is a connection between the members of the list (IList<TModel>), and the container (TListModel).

    There are several ways to handle this. One, as you discovered, is explicitly supplying the type to the lambda, though you can also explicitly supply the type arguments: @{Html.HeaderFor<IList<SomeModel>, SomeModel, int>(x => x.Price)}.

    For a practical solution, you can change the extension method definition. This change should provide the member type as part of the parameters to the extension method.

    public static MvcHtmlString HeaderFor<TModel, TValue>(this HtmlHelper<IList<TModel>> listViewModel, Expression<Func<TModel, TValue>> expression)
    {
        return new MvcHtmlString("a");
    }  
    

    This allows implicit type inference without compiler errors:

    @model IList<SomeModel>
    ...
    @Html.HeaderFor(x => x.Price)
    

    further reading: How can I return <TEnumerable, T> : where TEnumerable:IEnumerable<T> , see Jon Skeet's answer.