C#中的重载解析

3

在特定情况下,我在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

实际传递的实例是照片。

SearchResult(IEntity) 将在每个实例上调用,而应该调用 SearchResult(IPhoto)(或任何 IEntity 派生类的最具体重载版本)。我如何在不必诉诸此方法的情况下完成我的目标?

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

2
"我该如何做我想做的事情..." 你想做什么? - Andrew Savinykh
问题在于 IPhoto 继承自 IEntity,因此是您第一个 SearchResult 的有效参数。 - ChrisF
依赖注入;必须。 - Jake Petroules
这可能是相关的:http://stackoverflow.com/questions/7325638/is-there-a-way-to-resolve-nested-interfaces-on-dtos-in-a-razor-view - Andrew Savinykh
那个 var result 藏着什么实际类型? - AakashM
3个回答

4
你遇到这个问题是因为你的接口实现。像ChrisF指出的IPhoto 实现了 IContentItem ,后者又实现了IEntity 。关于重载决议的解释,文章《深入 C#:重载》提供了很好的解释,但总结一下:当C#决定调用哪个方法时,会忽略任何不正确的方法。从Microsoft有关重载决议的规范中可以得知:

重载决议是一种编译时机制,用于在给定参数列表和候选函数成员集合的情况下选择要调用的最佳函数成员。C#中的以下不同上下文中会选择要调用的函数成员:

调用调用表达式(第7.5.5节)中命名的方法。在对象创建表达式(第7.5.10.1节)中命名的实例构造函数调用。通过元素访问(第7.5.6节)调用索引器访问器。在表达式(第7.2.3节和第7.2.4节)中引用预定义或用户定义的运算符调用。这些上下文针对其自身的候选函数成员集合和参数列表进行详细描述,如上述各节所述。例如,方法调用的候选对象不包括标记为override(第7.3节)的方法,并且如果派生类中存在适用的方法,则基类中的方法不是候选对象(第7.5.5.1节)。

确定候选函数成员和参数列表之后,选择最佳函数成员在所有情况下都相同:

给定适用的候选函数成员集合,在该集合中查找最佳函数成员。如果集合仅包含一个函数成员,则该函数成员即为最佳函数成员。否则,最佳函数成员是一种函数成员,该函数成员相对于给定的参数列表比所有其他函数成员更好,前提是使用第7.4.2.2节中的规则将每个函数成员与所有其他函数成员进行比较。如果没有恰好一个函数成员优于所有其他函数成员,则函数成员调用是不明确的,并且将发生编译时错误。以下各节定义了适用函数成员和更好函数成员这些术语的确切含义。

为了说明问题,以下是来自上述重载文章的一些示例。

对于熟悉重载的人来说,他们会意识到在下面的例子中,当调用Foo("text")时,将使用static void Foo(string y)

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

所以,通过以上解释,你的情况是是最适合转换照片的选项,而这与是否存在重载无关。这与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 表达式仅迭代通过了适当的 helper 的特定类型。
  2. 使用一个带有 if 语句确定类型并将工作传递给适当方法的单一 helper。
  3. 思考你的实现策略是否真的是最佳选择。
  4. 在你的 IEntity 接口中放置一个渲染方法,并在迭代时调用它 (我最不喜欢的选项)

1
重载解析在各种情况下变得复杂 - 请参见http://csharpindepth.com/Articles/General/Overloading.aspx - Jon Skeet

3

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

选择哪个重载方法在编译时而不是运行时决定,因此实例的类型无论是什么,它都将始终调用SearchResult(IEntity entity)方法。

更新

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

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

是的,这很明显,这就是他抱怨的问题。现在解决这个问题会很有趣。 - Andrew Savinykh

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接