簡體   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