Question

I'm running into a problem with overload resolution in C# in a specific case. In my Razor file, I have the following:

@foreach (var result in Model.Result)
{
    @SearchResult(result)
}

@helper SearchResult(IEntity entity)
{
    <p>A normal thing</p>
}

@helper SearchResult(IPhoto photo)
{
    <p>A photo! Its title is @photo.Title</p>
}

Class structure:

interface IPhoto : IContentItem
interface IContentItem : IEntity

class Photo : ContentItem, IPhoto
class ContentItem : Entity, IContentItem
class Entity, IEntity

The actual instances being passed are of Photo.

SearchResult(IEntity) gets called for every instance when SearchResult(IPhoto) should be called (or the most specific overload of the instance of whatever the IEntity derivative is). How can I do what I'm trying to do without having to resort to this?

if (result is IXXX) { SearchResultXXX((IXXX)result) }
else if (result is IYYY) { SearchResultYYY((IYYY)result) }
...
Was it helpful?

Solution

You're running into this issue because of your interface implementation. Like ChrisF points out IPhoto implements IContentItem which implements IEntity. The article C# in Depth: Overloading provides a great explanation of overload resolution, but to summarize: overloading ignores any methods which can't be right when it's deciding which one to call. From the Microsoft spec on overload resolution:

Overload resolution is a compile-time mechanism for selecting the best function member to invoke given an argument list and a set of candidate function members. Overload resolution selects the function member to invoke in the following distinct contexts within C#:

Invocation of a method named in an invocation-expression (Section 7.5.5). Invocation of an instance constructor named in an object-creation-expression (Section 7.5.10.1). Invocation of an indexer accessor through an element-access (Section 7.5.6). Invocation of a predefined or user-defined operator referenced in an expression (Section 7.2.3 and Section 7.2.4). Each of these contexts defines the set of candidate function members and the list of arguments in its own unique way, as described in detail in the sections listed above. For example, the set of candidates for a method invocation does not include methods marked override (Section 7.3), and methods in a base class are not candidates if any method in a derived class is applicable (Section 7.5.5.1).

Once the candidate function members and the argument list have been identified, the selection of the best function member is the same in all cases:

Given the set of applicable candidate function members, the best function member in that set is located. If the set contains only one function member, then that function member is the best function member. Otherwise, the best function member is the one function member that is better than all other function members with respect to the given argument list, provided that each function member is compared to all other function members using the rules in Section 7.4.2.2. If there is not exactly one function member that is better than all other function members, then the function member invocation is ambiguous and a compile-time error occurs. The following sections define the exact meanings of the terms applicable function member and better function member.

To illustrate here are some examples from the aforementioned article on overloading.

Anyone familiar with overloading will realize that in the below example static void Foo(string y) will be used when the line Foo("text") is called.

class Test
{
    static void Foo(int x)
    {
        Console.WriteLine("Foo(int x)");
    }

    static void Foo(string y)
    {
        Console.WriteLine("Foo(string y)");
    }

    static void Main()
    {
        Foo("text");
    }
}

Here's something a bit more complex but better is more similar to your problem. The compiler is going to call Foo(int x) because it looks for the better function member rules which look at (amongst other things) what conversions are involved in going from each argument to the corresponding parameter type (int for the first method, double for the second).

class Test
{
    static void Foo(int x)
    {
        Console.WriteLine("Foo(int x)");
    }

    static void Foo(double y)
    {
        Console.WriteLine("Foo(double y)");
    }

    static void Main()
    {
        Foo(10);
    }
}

So with all of that explained what's going on in your case is that IEntity is the best conversion for a Photo irregardless of the fact that there is an IPhoto overload. This has nothing to do with the Razor @helper syntax. To illustrate that point the same "issue" is present with the below extension methods.

public static class SearchHelper
{
    public static MvcHtmlString SearchResult(this HtmlHelper helper,
        IEntity entity)
    {
        return new MvcHtmlString("A normal thing");
    }

    public static MvcHtmlString SearchResult(this HtmlHelper helper,
        IPhoto photo)
    {
        return new MvcHtmlString("A photo!");
    }
}

Finally, what I've covered here are simpler cases-- there are other oddities in overload resolution caused by generics, optional parameters, inheritance hierarchies, etc. So with all of that said as I see it you have a few options:

  1. Use a .Where lambda expression to iterate through only particular types passing them to the appropriate helper.
  2. Use a single helper with an if statement determining the type and passing the work on to the appropriate method.
  3. Think about whether your implementation strategy is really the best one.
  4. Put a rendering method in your IEntity interface and call that it when iterating (My least favorite option)

OTHER TIPS

What is the type of Property Model.Result? My guess is that it's IEntity.

The decision which overload will be called is done during compile time not run time, therefore it does not matter what the type of the instance is, it will always call the SearchResult(IEntity entity) method.

UPDATE

This is one possible solution to this problem:

@foreach (var result in Model.Result)
{
    @if(result is IPhoto)
    {
       @SearchResult(result as IPhoto)
    } 
    else 
    {
       @SearchResult(result)
    }
}

You could try using a double dispatch (ie: Visitor) pattern to get you a little closer. But you will still have to check to see if it is something that is not an IEntity (unless you have control over the IEntity interface).

interface IContentItem {
  void Accept(IContentVisitor visitor);
}

class Photo : IPhoto {
  void Accept(IContentVisitor visitor) { visitor.Visit(this); }
}

interface IContentVisitor<T>{
  T Visit(IPhoto photo);
  T Visit(IEntity entity);
}

class ContentVisitor : IContentVisitor<string>{
  string Visit(IPhoto photo) { return "<p>A normal thing</p>"; }
  string Visit(IEntity entity) { return "<p>A normal thing</p>"; }
}

var visitor = new ContentVisitor();
@foreach (var result in Model.Result)
{

    if(result is IContentItem)
       result.Accept(visitor);
    else //assuming result is IEntity
       visitor.Visit(result);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top