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 は、実装する実装を指摘し ます。C#の詳細: オーバーロードに関する記事では、オーバーロードの解決について詳しく説明していますが、要約すると、オーバーロードは、呼び出すメソッドを決定する際に正しくないメソッドを無視します。オーバーロードの解決に関する Microsoft の仕様から:IPhotoIContentItemIEntity

オーバーロードの解決は、引数リストと一連の候補関数メンバーを指定して、呼び出すのに最適な関数メンバーを選択するためのコンパイル時のメカニズムです。オーバーロードの解決では、C# 内の次の異なるコンテキストで呼び出す関数メンバーが選択されます。

invocation-expression で指定されたメソッドの呼び出し (セクション 7.5.5)。object-creation-expression で指定されたインスタンス コンストラクターの呼び出し (セクション 7.5.10.1)。element-access によるインデクサー アクセサーの呼び出し (セクション 7.5.6)。式で参照される定義済みまたはユーザー定義の演算子の呼び出し (セクション 7.2.3 およびセクション 7.2.4)。これらの各コンテキストは、上記のセクションで詳細に説明されているように、関数メンバー候補のセットと引数のリストを独自の方法で定義します。たとえば、メソッド呼び出しの候補のセットにはオーバーライドとマークされたメソッドは含まれず (セクション 7.3)、派生クラスのメソッドが適用可能な場合、基本クラスのメソッドは候補ではありません (セクション 7.5.5.1)。

候補の関数メンバーと引数リストが識別されると、最適な関数メンバーの選択はすべての場合で同じになります。

適用可能な候補関数メンバーのセットが与えられると、そのセット内の最適な関数メンバーが特定されます。セットに関数メンバーが 1 つしか含まれていない場合、その関数メンバーが最適な関数メンバーです。それ以外の場合、セクション 7.4.2.2 の規則を使用して各関数メンバーが他のすべての関数メンバーと比較される場合、最良の関数メンバーは、指定された引数リストに関して他のすべての関数メンバーよりも優れている 1 つの関数メンバーです。他のすべての関数メンバーよりも優れた関数メンバーが 1 つだけ存在しない場合、関数メンバーの呼び出しがあいまいになり、コンパイル時エラーが発生します。次のセクションでは、適用可能な関数メンバーとより良い関数メンバーという用語の正確な意味を定義します。

ここで説明するために、オーバーロードに関する前述の記事の例をいくつか示します。

オーバーロードに精通している人なら誰でも、以下の例で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");
    }
}

これはもう少し複雑ですが、より良いのはあなたの問題に似ています。コンパイラは、各引数から対応するパラメーターの型 (最初のメソッドは int、2 番目のメソッドは double) への変換に関係する変換を (とりわけ) 調べる、より適切な関数メンバーFoo(int x)規則を探すため、呼び出しを行います。

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があるという事実に関係なく、それが Photo の最良の変換であるということです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特定の型のみを反復処理し、適切なヘルパーに渡します。
  2. タイプを決定し、作業を適切なメソッドに渡す if ステートメントで単一のヘルパーを使用します。
  3. あなたの実装戦略が本当に最善のものかどうかを考えてください。
  4. IEntity インターフェイスにレンダリング メソッドを配置し、反復時にそれを呼び出します(私の最も嫌いなオプション)
于 2012-02-28T23:45:21.560 に答える
3

プロパティのタイプは何Model.Resultですか? 私の推測では、それはIEntity.

どのオーバーロードが呼び出されるかは、実行時ではなくコンパイル時に決定されるため、インスタンスの型に関係なく、常にSearchResult(IEntity entity)メソッドが呼び出されます。

アップデート

これは、この問題の解決策の 1 つです。

@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 に答える