2016-07-25 16 views
4

コンパイラが拡張メソッドの正しいオーバーロードを解決するのに問題があります。私が説明する最善の方法は、少しのコードです。この問題を示すLINQPadスクリプトがあります。私が手奇妙な拡張メソッドオーバーロードの解決

void Main(){ 
    new Container<A>().Foo(a=>false); 
} 

interface IMarker{} 
class A : IMarker{ 
    public int AProp{get;set;} 
} 
class B : IMarker{ 
    public int BProp{get;set;} 
} 
class Container<T>{} 

static class Extensions{ 
    public static void Foo<T>(this T t, Func<T, bool> func) 
     where T : IMarker{ 
     string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump(); 
    } 
    public static void Foo<T>(this Container<T> t, Func<T, bool> func){ 
     string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump(); 
    } 
} 

あり、エラーがある:

呼び出しは次のメソッドやプロパティの間であいまいです:「Extensions.Foo<Container<A>>(Container<A>, System.Func<Container<A>,bool>)」と 'Extensions.Foo<A>(Container<A>, System.Func<A,bool>)これは、私がいる問題でコンパイルされません'

まったくあいまいではないようです。最初の方法はContainer<T>を受け入れず、IMarkerのみを受け取ります。これは、一般的な制約がオーバーロードの解決を支援していないように思えるが、コードのこのバージョンでは、彼らがあるように見えるん:

void Main(){ 
    new A().Bar(); 
    new A().Foo(a=>a.AProp == 0); 
    new A().Foo(a=>false); // even this works 
    new A().Foo(a=>{ 
     var x = a.AProp + 1; 
     return false; 
    }); 

    new Container<A>().Bar(); 
    new Container<A>().Foo(a=>a.AProp == 0); 
    new Container<A>().Foo(a=>{ 
     var x = a.AProp + 1; 
     return false; 
    }); 
} 

interface IMarker{} 
class A : IMarker{ 
    public int AProp{get;set;} 
} 
class B : IMarker{ 
    public int BProp{get;set;} 
} 
class Container<T>{} 

static class Extensions{ 
    public static void Foo<T>(this T t, Func<T, bool> func) 
     where T : IMarker{ 
     string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump(); 
    } 
    public static void Foo<T>(this Container<T> t, Func<T, bool> func){ 
     string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump(); 
    } 

    public static void Bar<T>(this T t) where T : IMarker{ 
     string.Format("Bar({0}:IMarker)", typeof(T).Name).Dump(); 
    } 
    public static void Bar<T>(this Container<T> t){ 
     string.Format("Bar(Container<{0}>)", typeof(T).Name).Dump(); 
    } 
} 

これは、コンパイルし、期待される結果が得られます。

バー(A:IMarker)
フー(A:IMarker)
フー(A:IMarker)
フー(A:IMarker)
バー(コンテナ< >)
はFoo(コンテナ< >)
はFoo(コンテナ< >)

それだけContainer<T>で、私はラムダ式でラムダパラメータを参照しない場合にのみ、問題を持っているようだ、とクラス。 Barを呼び出すと、ラムダはなく、正常に動作します。ラムダパラメータに基づいて戻り値を持つFooを呼び出すと、正常に動作します。ラムダの戻り値が、たとえコンパイルされないがラムダパラメータが仮割り当てによって参照されているものと同じであっても、それは機能します。

なぜこれらのケースでは動作しますが、最初のケースでは機能しませんか?間違ったことをやっているのですか、あるいはコンパイラのバグを見つけましたか?私はC#4とC#6の両方の動作を確認しました。

+0

@ManoDestra:私は中括弧を置くのが好きではないので、編集は歓迎されません。 –

+0

これはC#です。したがって編集。 – ManoDestra

+0

私はあなたの意見を見ていません。 –

答えて

5

ああ、私は自分の答えを再読して得ました!いいえ質問=) オーバーロードを解決している間に制約where T:IMakerを考慮していないため、オーバーロードが機能しません(制約はメソッドシグネチャの一部ではありません)。あなたがラムダでパラメータを参照するときは、コンパイラにヒントを追加する(できる):

  1. をこの作品:

    new Container<A>().Foo(a => a.AProp == 0); 
    

    ここにいるので、私たちはそれをほのめかしん:A;タイプを推測するのに十分な情報がまだありませんので

    new Container<A>().Foo(a => a != null); 
    

  2. これはさえパラメータを参照して動作しません。

私の知る限りは、仕様を理解して、「Fooのシナリオ」に推論は、コールがあいまいなって第2の(機能)を引数に失敗する可能性があります。

型推論は、メソッド呼び出し(§14.5.5.1)のコンパイル時の処理の一部として発生するとのオーバーロードの解決工程の前に行われます。ここでは

は、SPEC(25.6.4)が言っていることです呼び出し。特定のメソッドグループがメソッド呼び出しで指定され、メソッド呼び出しの一部として型引数が指定されていない場合、メソッドグループ内の各汎用メソッドに型推論が適用されます。型推論が成功した場合、推論型引数を使用して、後続のオーバーロード解決の引数の型を決定します。

オーバーロードの解決が、呼び出すメソッドとして汎用メソッドを選択する場合、推論された型の引数は、呼び出しの実行時の型の引数として使用されます。特定のメソッドの型推論が失敗した場合、そのメソッドは過負荷解決に関与しません。型推論の失敗は、コンパイル時エラーを引き起こしません。しかし、多くの場合、オーバーロード解決が適用可能なメソッドを見つけられなかった場合、コンパイル時エラーが発生します。

ここで、かなり簡単な「バーシナリオ」になることができます。一つだけが適用されるので、型推論の後に我々は、一つだけメソッドを取得します:new Container<A>()ため

  1. Bar(Container<A>)(IMakerを実装していない)new A()ため
  2. Bar(A)が(コンテナではありません)

ECMA-334 specificationはここにあります。 P.私はそれが正しいことを100%確信しているわけではありませんが、私は本質的な部分を把握したと思っています。

+0

これまでのところ、制約はメソッドシグネチャの一部ではないので(これはまた、制約だけでメソッドをオーバーロードできないためです)、ラムダパラメータの使用法はコンパイラに明白にヒントを与えます過負荷を選択してください。私はこれを疑った。しかし、私がまだ得られないことは、なぜBarの呼び出しが2番目のスニペットで機能するのかです。彼らは完全にラムダを欠いている。 –

+0

私たちの答えは、言語仕様の25.6.4と14.4.2のどこかにあると思います。しかし、それは私のために7amだから、私は明日までどんなアイデアも考え出すことができません。 –

+0

あなたはその答えをもう少し詳しく調べてみたいと思いますか? –

1

Sergey私がやろうとしていたことがうまくいかない理由が分かったと思います。これは私が代わりにやろうとしていることです:

void Main(){ 
    new A().Bar(); 
    new A().Foo(a=>a.AProp == 0); 
    new A().Foo(a=>false); 

    new Container<A>().Bar(); 
    new Container<A>().Foo(a=>a.AProp == 0); 
    new Container<A>().Foo(a=>false); // yay, works now! 
} 

interface IMarker<T>{ 
    T Source{get;} 
} 

class A : IMarker<A>{ 
    public int AProp {get;set;} 
    public A Source{get{return this;}} 
} 
class B : IMarker<B>{ 
    public int BProp {get;set;} 
    public B Source{get{return this;}} 
} 

class Container<T> : IMarker<T>{ 
    public T Source{get;set;} 
} 

static class Extensions{ 
    public static void Foo<T>(this IMarker<T> t, Func<T, bool> func){} 
    public static void Bar<T>(this IMarker<T> t){} 
} 

残念なことに私にとっては、これは私のアプリケーションにとって大きな変更です。しかし、少なくとも拡張層はよりシンプルになり、最終的にはコンパイラと人間のあいまいさが少なくなり、良いことです。

+0

Containerが本当に意味のあるIMakerの場合は、素晴らしいリファクタリングのように見えます。 –

関連する問題