1. ホーム
  2. c#

[解決済み] C#コンパイラは、異なるベースクラスから派生した型に対して、なぜ「型が統一される可能性がある」と文句を言うのでしょうか?

2023-07-31 11:50:19

疑問点

私の現在の非コンパイルコードは以下のようなものです。

public abstract class A { }

public class B { }

public class C : A { }

public interface IFoo<T>
{
    void Handle(T item);
}

public class MyFoo<TA> : IFoo<TA>, IFoo<B>
    where TA : A
{
    public void Handle(TA a) { }
    public void Handle(B b) { }
}

C#コンパイラは、以下のルール/エラーを引用して、これをコンパイルすることを拒否します。

'MyProject.MyFoo<TA>' は 'MyProject.IFoo<TA>' と 'MyProject.IFoo<MyProject.B>' の両方を実装できない。なぜなら、これらはいくつかの型パラメータ置換に対して統一される可能性があるからである。

私はこのエラーの意味を理解しています。 TA が何でもあり得るのであれば、技術的には B にもなり得ますが、その場合、2つの異なる Handle の実装を曖昧にしてしまいます。

しかし、TA はできません。 は何でもありです。 型の階層を元に TA はできません。 である B - 少なくとも、私は と思う はできません。 TA から派生したものでなければなりません。 A を継承する必要があります。 ではなく から派生する B というように、C#/.NETでは複数のクラスを継承することはできません。

genericパラメータを削除し、代わりに TAC あるいは、さらに A であっても、コンパイルされます。

では、なぜこのエラーが出るのでしょうか? それはコンパイラのバグ、または一般的な非知性、あるいは私が見逃している他の何かがあるのでしょうか?

何か回避策があるのでしょうか。 MyFoo ジェネリッククラスを再実装しなければならないのでしょうか? TA のような派生型があるのでしょうか?

どのように解決するのですか?

これは、C# 4 の仕様の 13.4.2 項の結果です。

Cから作成される可能性のある構成型が、型引数がLに代入された後、 Lの2つのインターフェースが同一になる場合、Cの宣言は無効である。制約の宣言は、すべての可能な構成された型を決定する際に考慮されません。

そこの2番目の文に注意してください。

したがって、これはコンパイラのバグではなく、コンパイラが正しいのです。言語仕様の欠陥であると主張する人もいるかもしれません。

一般的に言って、制約は、一般的な型について事実を推論しなければならないほとんどすべての状況で無視されます。制約のほとんどは 有効な基底クラス を決定するために使用され、それ以外にはほとんど使用されません。

残念ながら、それは時に、あなたが発見したように、言語が不必要に厳密である状況を引き起こします。


一般的に、一般的な型引数によってのみ区別される何らかの方法で、同じインターフェイスを2回実装することは、悪いコードのにおいです。例えば class C : IEnumerable<Turtle>, IEnumerable<Giraffe> -- というのは奇妙なことで、C はカメの列なのです。 キリンの列であること。 と同時に ? ここで実際にやろうとしていることを説明できますか?本当の問題を解決するために、もっと良いパターンがあるかもしれません。


実際にあなたのインターフェイスがあなたが説明した通りであるなら。

interface IFoo<T>
{
    void Handle(T t);
}

次に、インターフェースの多重継承が別の問題を引き起こします。このインターフェイスをcontravariantにすることを合理的に決定できるかもしれません。

interface IFoo<in T>
{
    void Handle(T t);
}

ここで

interface IABC {}
interface IDEF {}
interface IABCDEF : IABC, IDEF {}

そして

class Danger : IFoo<IABC>, IFoo<IDEF>
{
    void IFoo<IABC>.Handle(IABC x) {}
    void IFoo<IDEF>.Handle(IDEF x) {}
}

そして今、事態は本当におかしくなっています...

IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);

どのHandleの実装が呼び出されるのか ???

この問題についての考察は、この記事とコメントをご覧ください。

http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx