繁体   English   中英

C#中的重载分辨率

[英]Overload resolution in C#

在特定情况下,我遇到了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) }
...

由于您的接口实现,您遇到了这个问题。 ChrisF指出 IPhoto实现IContentItem它实现IEntity 深度中的文章C#:重载提供了重载解析的一个很好的解释,但总结一下:重载忽略了在决定调用哪一个时不能正确的任何方法。 从Microsoft规范的重载决议

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

调用invocation-expression中指定的方法(第7.5.5节)。 调用在object-creation-expression中命名的实例构造函数(第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);
    }
}

因此,所有的这解释这是怎么回事,你的情况是, IEntity是一个事实,即存在一个照片irregardless最佳转换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界面中放置一个渲染方法,并在迭代时调用它(我最不喜欢的选项)

什么是Property Model.Result 我的猜测是它是IEntity

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

UPDATE

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM