4 に答える
それは複雑です。
b.Cloneの呼び出しは、明らかにBCを呼び出す必要があります。ここにはインターフェースはまったく含まれていません!呼び出すメソッドは、コンパイル時の分析によって完全に決定されます。したがって、Baseのインスタンスを返す必要があります。これはあまり面白くありません。
対照的に、cb.Cloneの呼び出しは非常に興味深いものです。
行動を説明するために確立しなければならないことが2つあります。最初に:どの「スロット」が呼び出されますか?第二に:そのスロットにはどのような方法がありますか?
実装する必要のあるメソッドが2つあるため、Derivedのインスタンスには2つのスロットが必要ICloneable<Derived>.Clone
ですICloneable<Base>.Clone
。それらのスロットをICDCおよびICBCと呼びましょう。
明らかに、cb.Cloneによって呼び出されるスロットはICBCスロットである必要があります。コンパイラがスロットICDCがタイプのcbにも存在することを知る理由はありませんICloneable<Base>
。
そのスロットにはどのような方法がありますか?Base.CloneとDerived.Cloneの2つのメソッドがあります。それらをBCとDCと呼びましょう。ご存知のように、Derivedのインスタンスのそのスロットの内容はDCです。
これは奇妙に思えます。明らかにスロットICDCの内容はDCでなければなりませんが、なぜスロットICBCの内容もDCである必要があるのでしょうか。この動作を正当化するC#仕様に何かありますか?
最も近いのはセクション13.4.6で、これは「インターフェースの再実装」に関するものです。簡単に言えば、あなたが言うとき:
class B : IFoo
{
...
}
class D : B, IFoo
{
...
}
次に、IFooのメソッドに関する限り、Dで最初から始めます。BのどのメソッドがIFooのメソッドにマップされるかについてBが言わなければならないことはすべて破棄されます。Dは、Bが行ったのと同じマッピングを選択する場合もあれば、まったく異なるマッピングを選択する場合もあります。この動作は、予期しない状況につながる可能性があります。あなたはここでそれらについてもっと読むことができます:
http://blogs.msdn.com/b/ericlippert/archive/2011/12/08/so-many-interfaces-part-two.aspx
しかし:ICloneable<Derived>
の再実装の実装はICloneable<Base>
?あるべきかどうかはまったく明らかではありません。IFooのインターフェースの再実装は、IFooのすべての基本インターフェースの再実装ですが、 !の基本インターフェースでICloneable<Base>
はありません。ICloneable<Derived>
これがインターフェースの再実装であると言うのは確かに一筋縄ではいきません。仕様はそれを正当化するものではありません。
では、ここで何が起こっているのでしょうか。
ここで起こっているのは、ランタイムがスロットICBCを埋める必要があるということです。(すでに述べたように、スロットICDCは明らかにメソッドDCを取得する必要があります。)ランタイムはこれがインターフェイスの再実装であると見なすため、DerivedからBaseまで検索して、最初の一致を実行します。DCは分散のおかげで一致するため、BCに勝ちます。
ここで、CLI仕様のどこでその動作が指定されているかを尋ねると、答えは「どこにもありません」です。実際、状況はそれよりもかなり悪いです。CLI仕様を注意深く読むと、実際には反対の動作が指定されていることがわかります。技術的には、CLRはここでの独自の仕様に準拠していません。
ただし、ここで説明する正確なケースを検討してください。Derivedのインスタンスを呼び出す誰かICloneable<Base>.Clone()
が、Derivedを取り戻したいと考えるのは合理的です!
C#に分散を追加したとき、もちろん、ここで言及したシナリオそのものをテストし、最終的に、動作が不当で望ましいものであることがわかりました。その後、この望ましい動作が仕様によって正当化されるように仕様を編集する必要があるかどうかについて、CLI仕様の管理者との交渉期間が続きました。その交渉の結果がどうだったか思い出せません。私は個人的には関わっていませんでした。
したがって、要約すると:
- 事実上、CLRは、これがインターフェイスの再実装であるかのように、派生からベースへの最初の一致検索を実行します。
- De jure、それはC#仕様またはCLI仕様のどちらによっても正当化されません。
- 人を壊さずに実装を変更することはできません。
- 分散変換の下で統合するインターフェースを実装することは危険で混乱を招きます。それを避けてください。
バリアントインターフェイスの統合により、CLRの「最初の適合」実装で、実装に依存する不当な動作が明らかになる別の例については、以下を参照してください。
また、インターフェイスメソッドの非バリアントの一般的な統合が、CLRの「ファーストフィット」実装における不当な実装依存の動作を明らかにする例については、以下を参照してください。
https://ericlippert.com/2006/04/05/odious-ambiguous-overloads-part-one/ https://ericlippert.com/2006/04/06/odious-ambiguous-overloads-part-two/
その場合、プログラムのテキストを並べ替えることで、実際にプログラムの動作を変更できます。これは、C#では本当に奇妙なことです。
それは1つの意味しか持つことができません:メソッドはとの両方を new public Derived Clone()
実装します。非表示のメソッドを呼び出すための明示的な呼び出しのみ。 ICloneable<Base>
ICloneable<Derived>
Base.Clone()
私はそれが呼び出すためだと思います:
ICloneable<Base> cb = d;
分散がない場合は、をcb
表すことしかできませんICloneable<Base>
。しかし、分散があると、を表すこともできます。これは、にキャストするよりもICloneable<Derived>
明らかに近く、キャストが優れています。d
ICloneable<Base>
仕様の関連部分は、2つの可能な暗黙の参照変換のどちらが割り当てに対して機能するかを制御する部分であるように思われますICloneable<Base> cb = d;
。セクション6.1.6「暗黙の参照変換」から取られた2つの選択肢は次のとおりです。
- SがTを実装する場合、任意のクラスタイプSから任意のインターフェイスタイプTまで。
(ここでは、セクション13.4に従って、「クラスCがインターフェイスを直接実装する場合、Cから派生したすべてのクラスも暗黙的にインターフェイスを実装する」Derived
ため、を実装し、直接実装するため、暗黙的に実装します。)ICloneable<Base>
Base
ICloneable<Base>
Derived
- インターフェイスまたはデリゲート型T0への暗黙のIDまたは参照変換があり、T0がTへの分散変換可能(§13.1.3.2)である場合、任意の参照型からインターフェイスまたはデリゲート型Tへ。
(ここでは、直接実装しているため、Derived
暗黙的にに変換可能であり、分散変換可能です。)ICloneable<Derived>
ICloneable<Derived>
ICloneable<Base>
しかし、暗黙の参照変換を明確にすることを扱う仕様の部分を見つけることができません。