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

实际传递的实例是 Photo。

SearchResult(IEntity)在应该调用的每个实例SearchResult(IPhoto)(或 IEntity 派生的任何实例的最具体的重载)中调用。我怎样才能做我想做的事而不必诉诸于此?

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

3 回答 3

4

由于您的接口实现,您遇到了这个问题。像ChrisF 指出 IPhotoimplements IContentItemwhich implements IEntity。文章C# in Depth: Overloading对重载决议提供了很好的解释,但总结一下:重载在决定调用哪个方法时会忽略任何不正确的方法。来自微软关于重载解析的规范:

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

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

一旦确定了候选函数成员和参数列表,最佳函数成员的选择在所有情况下都是相同的:

给定一组适用的候选函数成员,找到该集合中的最佳函数成员。如果该集合仅包含一个函数成员,则该函数成员是最佳函数成员。否则,如果使用第 7.4.2.2 节中的规则将每个函数成员与所有其他函数成员进行比较,则最佳函数成员是在给定参数列表方面优于所有其他函数成员的一个函数成员。如果没有一个函数成员比所有其他函数成员更好,那么函数成员调用是不明确的,并且会发生编译时错误。以下部分定义了术语适用函数成员和更好的函数成员的确切含义。

为了说明这里是前面提到的关于重载的文章中的一些例子。

任何熟悉重载的人都会意识到,在下面的示例中将在调用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. 使用.Wherelambda 表达式仅遍历特定类型,将它们传递给适当的帮助器。
  2. 使用带有 if 语句的单个助手来确定类型并将工作传递给适当的方法。
  3. 想想你的实施策略是否真的是最好的。
  4. 在您的 IEntity 接口中放置一个渲染方法并在迭代时调用它(我最不喜欢的选项)
于 2012-02-28T23:45:21.560 回答
3

什么是财产类型Model.Result?我的猜测是它是IEntity.

调用哪个重载的决定是在编译时而不是运行时完成的,因此不管实例的类型是什么,它总是会调用该SearchResult(IEntity entity)方法。

更新

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

@foreach (var result in Model.Result)
{
    @if(result is IPhoto)
    {
       @SearchResult(result as IPhoto)
    } 
    else 
    {
       @SearchResult(result)
    }
}
于 2012-02-28T23:41:53.157 回答
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);
}
于 2012-02-28T23:55:58.537 回答