C#中的重载分辨率

时间:2012-02-28 22:59:46

标签: c# razor polymorphism overload-resolution

在特定情况下,我遇到了C#中的重载解析问题。在我的Razor文件中,我有以下内容:

@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>
}

班级结构:

interface IPhoto : IContentItem
interface IContentItem : IEntity

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

传递的实际实例是Photo。

当应该调用SearchResult(IEntity)时(或者IEntity派生的实例的最具体的重载),每个实例都会调用

SearchResult(IPhoto)。如何在不必诉诸于此的情况下做我想做的事情?

if (result is IXXX) { SearchResultXXX((IXXX)result) }
else if (result is IYYY) { SearchResultYYY((IYYY)result) }
...

3 个答案:

答案 0 :(得分:4)

由于您的界面实施,您遇到了这个问题。与ChrisF points out IPhoto实现IContentItem实现IEntity一样。文章C# in Depth: Overloading提供了重载解析的一个很好的解释,但总结一下:重载忽略了在决定调用哪一个时不能正确的任何方法。来自overload resolution上的Microsoft规范:

  

重载解析是一种选择最佳解决方案的编译时机制   函数成员调用给定的参数列表和一组   候选职能成员。重载分辨率选择该功能   要在C#中的以下不同上下文中调用的成员:

     

调用invocation-expression中指定的方法(Section   7.5.5)。调用在object-creation-expression中命名的实例构造函数(第7.5.10.1节)。调用一个   索引器访问器通过元素访问(第7.5.6节)。调用   在表达式中引用的预定义或用户定义的运算符   (第7.2.3节和第7.2.4节)。这些上下文中的每一个都定义了   候选函数成员集和自己的参数列表   独特的方式,如上面列出的部分中详细描述的那样。对于   例如,方法调用的候选集不会   包括标记为覆盖的方法(第7.3节)和基础中的方法   如果派生类中的任何方法是,则class不是候选者   适用(第7.5.5.1节)。

     

候选函数成员和参数列表一旦出现   确定后,最佳功能成员的选择是相同的   所有情况:

     

考虑到适用的候选函数成员集,最好   该集中的函数成员位于。如果集合只包含一个   函数成员,那个函数成员是最好的函数   会员。否则,最好的函数成员是一个函数成员   这比所有其他功能成员更好   给定参数列表,前提是将每个函数成员进行比较   所有其他函数成员使用第7.4.2.2节中的规则。如果   没有一个功能成员比其他功能成员更好   函数成员,然后函数成员调用是模糊的   发生编译时错误。以下部分定义了确切的   适用的职能成员和更好的职能的含义   构件。

这里举例说明上述article on overloading中的一些例子。

任何熟悉重载的人都会意识到,在下面的示例中,static void Foo(string y)将在调用行Foo("text")时使用。

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");
    }
}

这里有点复杂但更好的更类似于你的问题。编译器将调用Foo(int x),因为它查找更好的函数成员规则,这些规则查看(从其他方面)从每个参数到相应参数类型所涉及的转换(第一个方法的int,第二个方法的double。)

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);
    }
}

所有这些都解释了你的情况发生了什么,IEntity是照片的最佳转换,无论有IPhoto超载这一事实。这与Razor @helper语法无关。为了说明这一点,下面的扩展方法存在相同的“问题”。

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!");
    }
}

最后,我在这里介绍的是更简单的情况 - 由于泛型,可选参数,继承层次结构等引起的重载解析还有其他奇怪之处。所以我所看到的所有内容都说明了你有几个选择:

  1. 使用.Where lambda表达式仅迭代将特定类型传递给相应帮助程序的特定类型。
  2. 使用带有if语句的单个帮助程序确定类型并将工作传递给适当的方法。
  3. 考虑一下您的实施策略是否真的是最好的。
  4. 在IEntity界面中放置渲染方法,并在迭代(我最不喜欢的选项)时调用它

答案 1 :(得分:3)

属性Model.Result的类型是什么?我的猜测是IEntity

决定哪个重载将在编译时而不是运行时完成,因此实例的类型无关紧要,它总是调用SearchResult(IEntity entity)方法

<强>更新

这是解决此问题的一种可能方法:

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

答案 2 :(得分:0)

您可以尝试使用双重调度(即:访问者)模式让您更近一点。但是你仍然需要检查它是否是不是IEntity的东西(除非你能控制IEntity界面)。

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);
}