C# 中的重载解析

本文关键字:重载 | 更新日期: 2023-09-27 18:26:08

在特定情况下,我在 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(IPhoto)时,SearchResult(IEntity) 都会为每个实例调用(或任何 IEntity 导数的实例的最具体的重载(。我怎样才能做我想做的事情而不必求助于这个?

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

C# 中的重载解析

由于接口实现,您遇到了此问题。就像ChrisF指出的那样,IPhoto实现了IEntity IContentItem。文章 C# 深入:重载提供了对重载解析的很好的解释,但总结一下:重载会忽略任何在决定调用哪个方法时不正确的方法。从过载分辨率的Microsoft规范:

重载解析是一种用于选择最佳 给定参数列表和一组 候选函数成员。重载分辨率选择函数 要在 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 节中的规则的所有其他函数成员。如果 没有一个函数成员比所有其他函数成员更好 函数成员,则函数成员调用不明确且 发生编译时错误。以下各节定义了确切的 术语适用函数成员和更好函数的含义 成员。

为了说明这一点,这里有上述关于重载的文章的一些示例。

任何熟悉重载的人都会意识到,在下面的示例中,调用行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);
    }
}
因此,在

解释所有这些之后,您在这种情况下发生的事情是,无论存在IPhoto过载的事实,IEntity都是照片的最佳转换。这与 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接口中放置一个渲染方法,并在迭代时调用它(我最不喜欢的选项(

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

调用

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

更新

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

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

您可以尝试使用双重调度(即:访客(模式来让您更接近一点。 但是您仍然必须检查它是否不是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);
}