2009-05-28 13 views
6

今日、私は困惑したコンパイルの問題で走った。これら2つのコンテナクラスを考えてみましょう。C#コンパイラのジェネリック、継承、および失敗したメソッドの解決

public class BaseContainer<T> : IEnumerable<T> 
{ 
    public void DoStuff(T item) { throw new NotImplementedException(); } 

    public IEnumerator<T> GetEnumerator() { } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { } 
} 
public class Container<T> : BaseContainer<T> 
{ 
    public void DoStuff(IEnumerable<T> collection) { } 

    public void DoStuff <Tother>(IEnumerable<Tother> collection) 
     where Tother: T 
    { 
    } 
} 

前者定義DoStuff(T item)特にDoStuff <Tother>(IEnumerable<Tother>)後者オーバーロードは(4までIが聞く)C位のcovariance/contravarianceの不在を回避します。

このコード

Container<string> c = new Container<string>(); 
c.DoStuff("Hello World"); 

はかなり奇妙なコンパイルエラーに当たります。メソッド呼び出しから<char>が存在しないことに注意してください。

汎用型またはメソッド 'Container.DoStuff(System.Collections.Generic.IEnumerable)'の型パラメータ 'Tother'として 'char'型を使用することはできません。 'char'から 'string'へのボクシング変換はありません。

基本的に、コンパイラはContainer.DoStuff<char>(IEnumerable<char>)stringため、実装IEnumerable<char>DoStuff(string)に私の呼び出しをジャムではなく、BaseContainer.DoStuff(string)を使用しようとしています。

私はこのコンパイルをするために見つけた唯一の方法は、派生クラスなぜコンパイラは1)それはそれができる知っているIEnumerable<char>」として文字列をジャムしようとしている

public class Container<T> : BaseContainer<T> 
{ 
    public new void DoStuff(T item) { base.DoStuff(item); } 

    public void DoStuff(IEnumerable<T> collection) { } 

    public void DoStuff <Tother>(IEnumerable<Tother> collection) 
     where Tother: T 
    { 
    } 
} 

DoStuff(T)を追加することですt(コンパイルエラーがあるとします)、2)コンパイルがうまくいった基本クラスのメソッドがありますか? C#のジェネリックスや仮想メソッドに関することを誤解していますか? をContainerに追加する以外の方法がありますか?

+3

を使用してからあなたを停止しているもの、私は、これは奇妙なようだ同意するが、それは仕様に応じて正しいです。これは、2つのルールの相互作用の結果です。(1)オーバーロード解決の適用可能性検査は制約チェックの前に行われます。(2)派生クラスの適用可能なメソッドは基本クラスの適用可能メソッドよりも常に優れています。どちらも合理的に合理的なルールです。彼らはちょうどあなたのケースで特にひどく相互作用するように起こります。 –

+1

詳細については、7.5.5.1項を参照してください。具体的には、(1)「最適なメソッドが汎用メソッドであれば、型引数(指定または推論された)が制約と照合されます...」および(2) "候補メソッドのセットは、最も派生したタイプのメソッドだけを含むように縮小されています... " –

+2

最終的にここでの問題は設計上の問題です。あなたは、 "DoStuff"というメソッドに "T型の単一の値を扱う"と "T型の値のシーケンスを扱う"の両方を意味するようにオーバーロードしています。これは、 "タイプT"それ自体がシーケンスであるなど、多くの点で深刻な "意図解決"問題に繋がります。この問題を回避するために、BCLの既存のコレクションクラスが慎重に設計されていることがわかります。項目を取るメソッドは "Frob"と呼ばれ、項目のシーケンスを取るメソッドは "FrobRange"と呼ばれます(例えば、リストに "Add"と "AddRange"など)。 –

答えて

3

編集

[OK]を...私は今、あなたの混乱を参照してくださいと思います。 DoStuff(文字列)がパラメータを文字列として保持し、BaseClassメソッドリストを最初に探して適切なシグニチャを探し、その代替を失敗して別の型にキャストしようとしました。

しかし、それ以外のところで起こった...代わりにContainer.DoStuff(string)、meh "theresは法案に合った基本クラスメソッドですが、私はIEnumerableに変換して利用可能なものについて心臓発作を起こすでしょう現在のクラスではなく...

うーん...私は、この特定のコーナーケースをカバー

オリジナルジョンやマークは、特定のC#スペック段落で、この時点でチャイムすることができるだろうと確信している

両方のメソッドは、IEnumerable Col lee

あなたは個別の文字列を渡しています。

コンパイラはその文字列を取り、起こっている、

[OK]を、私は文字列を持って、どちらの方法 はIEnumerable<T>を期待して、だから私は

を完了... IEnumerable<char>にこの文字列を回す ます

右、最初の方法を確認してください... うーん...このクラスは Container<string>ですが、私は IEnumerable<char>はとてもそれは正しくありません持っています。

第二の方法を確認し、うーん....私はそれが右のいずれか ではありませんので、文字列を実装していない IEnumerable<char>が、文字を持ってい 。

コンパイラエラー

ので修正、よくそれは完全にあなたが達成しようとするもの...次の両方が有効になり、基本的に、あなたの種類の使用がでちょうど間違っている依存何#Sあなたの化身。

 Container<char> c1 = new Container<char>(); 
     c1.DoStuff("Hello World"); 

     Container<string> c2 = new Container<string>(); 
     c2.DoStuff(new List<string>() { "Hello", "World" }); 
+0

コンパイラは、パラメータの型だけでオーバーロードを選択します(パラメータ+制約の全体ではありません)。それはむしろ弱く、半分焼いたようです。 * *使用できる方法を見つけることができますが、それを選択することはできません。 –

+0

いいえ...あなたのメソッドのどちらも、あなたの渡した型を満たすことができません。コンテナは現在、DoStuff(IEnumerable )またはDoStuff(IEnumerable )< - 密閉クラス。 –

+0

できませんか?何がBaseContainer.DoStuff(T)に起こったのですか?あなたの修正の編集について。私は文字列のコンテナと文字列 "Hello World"がDoStuffされることを望みます。私がクラスを修正できない場合は、DoStuff(新しい文字列[] {"Hello World"});私が欲しいものですが、それは本当に面白いAPIです(私は思っています)。 –

1

私はcharが値型であり、stringが参照型であるという事実と関係があると思います。あなたが定義しているように見えます

TOther : T 

とcharは文字列から派生していません。

+0

それで、私が呼び出す方法としてDoStuff (IEnumerable )を選んでいる理由がわかりません。私はDoStuffを指定していません文字列は文字列から派生していません。それは決して意味をなさない。 –

+0

文字列をIEnumerable にキャストでき、コンパイラがTotherをcharと推定しているのだろうか? – n8wrl

+0

それはまさにそれがやっていることだと思うが、私はDoStuffを起動していないよ、ちょうどDoStuff。私はそのようなジェネリックを想定したことはありません。 –

2

コンパイラは、パラメータをIEnumerable <T>に一致させようとします。 String型はIEnumerable <char>を実装しているので、Tは "char"とみなされます。

その後、コンパイラは「OtherT:T」という条件を満たしていないかどうかをチェックします。したがって、コンパイラのエラーです。

2

私は実際にはわからないので、推測です。まず、派生クラスでメソッド呼び出しを解決することです(オブジェクトは派生型のオブジェクトなので)。それができない場合にのみ、それを解決するために基本クラスのメソッドを調べることに移ります。あなたの場合は、

DoStuff <Tother>(IEnumerable<Tother> collection) 

オーバーロードを使用して解決することができるので、それを詰まらせようとしました。したがって、パラメータに関してはそれを解決することはできますが、制約上の不具合にぶつかります。その時点で、それはすでにあなたの過負荷を解決しているので、それ以上は見えませんが、ただエラーをスローします。理にかなっている?

+0

私はそれを指定していないという事実にかかわらず、暗黙のうちにTother = charと仮定します。私は引数を1つにまとめることができるので、単純に仮定した方法でジェネリックを見たことはありません。 –

+1

LINQを使ったことがありますか?ほとんどのLINQは、型パラメーターがメソッドパラメーターの1つで提供されている場合、型パラメーターが想定されるという考えに基づいています。 –

+2

IEnumerable (別名「文字列」)を渡すことによって、Totherがcharであることを指定しています。あなたはC#の "メソッド型推論"機能に慣れていないかもしれませんが、C#2.0以来のことです。メソッド型推論アルゴリズムの仕組みについては、私のブログの "型推論"のセクションを参照してください。彼らはかなり魅力的です。 –

3

エリックリペットは説明したように、それは制約をチェックする前に方法を選択するため、コンパイラはDoStuff<Tother>(IEnumerable<Tother>) where Tother : T {}方法を選択します。 stringはIEnumerable<>を実行できるので、コンパイラはそれをその子クラスのメソッドと照合します。 コンパイラは、C#の仕様に記載されているように、正しくを使用しています。

DoStuffをextension methodとして実装することで、メソッドの解決順序を強制することができます。 拡張メソッドは後、基本クラスのメソッドをチェックしているので、それがDoStuff<T>に対してそれを一致させることを試みた後までDoStuffさんIEnumerable<Tother>に対するstringに一致するようにしようとしません。

次のコードは、必要なメソッドの解決順序、共分散、および継承を示しています。新しいプロジェクトにコピーして貼り付けてください。

私がこれまでの考えることができる。この最大の欠点は、あなたがオーバーライドメソッドでbaseを使用することができないということですが、私は(あなたが興味を持っているかどうか尋ねる)ことを回避する方法があると思います。ここで

using System; 
using System.Collections.Generic; 

namespace MethodResolutionExploit 
{ 
    public class BaseContainer<T> : IEnumerable<T> 
    { 
     public void DoStuff(T item) { Console.WriteLine("\tbase"); } 
     public IEnumerator<T> GetEnumerator() { return null; } 
     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return null; } 
    }   
    public class Container<T> : BaseContainer<T> { } 
    public class ContainerChild<T> : Container<T> { } 
    public class ContainerChildWithOverride<T> : Container<T> { } 
    public static class ContainerExtension 
    { 
     public static void DoStuff<T, Tother>(this Container<T> container, IEnumerable<Tother> collection) where Tother : T 
     { 
      Console.WriteLine("\tContainer.DoStuff<Tother>()"); 
     } 
     public static void DoStuff<T, Tother>(this ContainerChildWithOverride<T> container, IEnumerable<Tother> collection) where Tother : T 
     { 
      Console.WriteLine("\tContainerChildWithOverride.DoStuff<Tother>()"); 
     } 
    } 

    class someBase { } 
    class someChild : someBase { } 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("BaseContainer:"); 
      var baseContainer = new BaseContainer<string>(); 
      baseContainer.DoStuff(""); 

      Console.WriteLine("Container:"); 
      var container = new Container<string>(); 
      container.DoStuff(""); 
      container.DoStuff(new List<string>()); 

      Console.WriteLine("ContainerChild:"); 
      var child = new ContainerChild<string>(); 
      child.DoStuff(""); 
      child.DoStuff(new List<string>()); 

      Console.WriteLine("ContainerChildWithOverride:"); 
      var childWithOverride = new ContainerChildWithOverride<string>(); 
      childWithOverride.DoStuff(""); 
      childWithOverride.DoStuff(new List<string>()); 

      //note covariance 
      Console.WriteLine("Covariance Example:"); 
      var covariantExample = new Container<someBase>(); 
      var covariantParameter = new Container<someChild>(); 
      covariantExample.DoStuff(covariantParameter); 

      // this won't work though :(
      // var covariantExample = new Container<Container<someBase>>(); 
      // var covariantParameter = new Container<Container<someChild>>(); 
      // covariantExample.DoStuff(covariantParameter); 

      Console.ReadKey(); 
     } 
    } 
} 

が出力されます。

BaseContainer: 
     base 
Container: 
     base 
     Container.DoStuff<Tother>() 
ContainerChild: 
     base 
     Container.DoStuff<Tother>() 
ContainerChildWithOverride: 
     base 
     ContainerChildWithOverride.DoStuff<Tother>() 
Covariance Example: 
     Container.DoStuff<Tother>() 

あなたの周りこの作品で何か問題を見ることはできますか?

+0

うーん、興味深い考え。私はそのクラスを非常に基本的に保つために、BaseContainerにこれらのオーバーロードを入れませんでした。私は簡単にDoStuff をBaseContainerに置くことができましたが、その種のものはBaseContainerを基本的に保つという私の目標を打ち破っています。拡張は、私が技術的にBaseContainerを私が望む方法で保つことを意味します。:) –

+0

Colin、それは* BaseContainerにオーバーロードを置かない*。つまり、BaseContainerのインテリセンスに迷惑をかけません。それを試してください。私が誤解した場合は、明確にしてください。 – dss539

+0

私は申し訳ありませんが、私は方法のプロトタイプを誤解しました。しかし、ジェネリッククラスの拡張機能を使用すると、c.DoStuff の代わりに、のc.DoStuffで呼び出す必要があります。 –

0

私はあなたが達成しようとしているもので、実際に明確ではないよ、ちょうど2つの方法、DoStuff(T item) and DoStuff(IEnumerable<T> collection)?

+0

問題は、コンパイラがDoStuff(T)ではなくDoStuff (IEnumerable )にラッチするため、T自体がIEnumerable であるためです。 –

関連する問題