Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't c# support an object with an interface as a parameter?

I have the following class declaration:

public class EntityTag : BaseEntity, ITaggable

I have an Html helper method:

public static string TagCloud(this HtmlHelper html, IQueryable<ITaggable> taggables, 
  int numberOfStyleVariations, string divId)

This is my ASP.NET MVC ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IQueryable<EDN.MVC.Models.EntityTag>>" %>
<%@Import Namespace="EDN.MVC.Helpers" %>
<%= Html.TagCloud(Model, 6, "entity-tags") %>

When I pass in an IQueryable collection to the ascx, I get this error:

Compiler Error Message: CS1928: 'System.Web.Mvc.HtmlHelper>' does not contain a definition for 'TagCloud' and the best extension method overload 'EDN.MVC.Helpers.EdnHelpers.TagCloud(System.Web.Mvc.HtmlHelper, System.Linq.IQueryable, int, string)' has some invalid arguments

If I try to explicitly convert the object collection with this:

    public static string TagCloud(this HtmlHelper html, IQueryable<Object> taggables, int numberOfStyleVariations, string divId)
    {
        var tags = new List<ITaggable>();
        foreach (var obj in taggables)
        {
            tags.Add(obj as ITaggable);
        }
        return TagCloud(html, tags.AsQueryable(), numberOfStyleVariations, divId);
    }

I get the same error - the values I'm passing in are not liked by the compiler.

Shouldn't my EntityTag class automatically be supported as IQueryable? What am I missing? It's got to be something obvious. (I hope.)

like image 240
John Kaster Avatar asked Jan 08 '10 01:01

John Kaster


2 Answers

Essentially, you're trying to pass an object of the non-generic type IQueryable to a method that accepts the generic IQueryable<ITaggable>, which the compiler cannot "match", resulting in the CS1928 (since the two types are, in fact, different).

In your overload that accepts an IQueryable<object> (which is already doing the necessary conversion to a generic list), you simply need to call the generic version of AsQueryable instead of the non-generic one, as such:

public static string TagCloud(this HtmlHelper html, IQueryable taggables, int numberOfStyleVariations, string divId)  
{  
    var tags = new List<ITaggable>();  
    foreach (var obj in taggables)  
    {  
        tags.Add(obj as ITaggable);  
    }  
    return TagCloud(html, tags.AsQueryable<ITaggable>(), numberOfStyleVariations, divId);  
}  

Allow me to add, as well, that IQueryable<T> derives from IQueryable, meaning that not all IQueryable objects are IQueryable<T>, thus making the conversion necessary. If the situation were reversed, i.e. your "real" helper method was defined to handle IQueryable objects, then you certainly would have no problem passing an IQueryable<T> to that method (since all IQueryable<T> objects are, in fact, IQueryable).

Per Craig Stuntz, a much more elegant solution using LINQ features: <%= Html.TagCloud(Model.Select(t => (ITaggable)t), 6, "entity-tags") %>. You can also use <%= Html.TagCloud(Model.Cast<ITaggable>(), 6, "entity-tags") %> if your queryable provider supports it.

like image 107
SteveH Avatar answered Oct 08 '22 06:10

SteveH


C# 4.0 will support it. Search for "Covariance and Contravariance in C# 4"

like image 24
deerchao Avatar answered Oct 08 '22 07:10

deerchao