C#でのジェネリックとの共分散反分散を理解する問題


115

次のC#コードがコンパイルされない理由を理解できません。

ご覧のとおり、静的なジェネリックメソッドSomething with a IEnumerable<T>parameter(and Tis constrained to an IAinterface)があり、このパラメーターは暗黙的に次のように変換できません。IEnumerable<IA>

説明は何ですか?(回避策を探すのではなく、なぜそれが機能しないのかを理解するためです)。

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}

エラーが発生しましSomething2(bar)た:

引数1:「System.Collections.Generic.List」から「System.Collections.Generic.IEnumerable」に変換できません



12
T参照タイプに制限されていません。この条件を使用すれば、where T: class, IA動作するはずです。リンクされた回答に詳細があります。
Dirk

2
@Dirkこれは重複としてフラグを立てるべきではないと思います。ここでの概念の問題は値型の面での共分散/反変の問題であることは事実ですが、ここでの特定のケースは「このエラーメッセージの意味」です。今後のユーザーは、このエラーメッセージを検索し、この投稿を見つけて、満足していただけると思います。(私がよく行うように)
Reginald Blue

Something2(foo);直接言うだけでも状況を再現できます。周りに行く.ToList()取得することList<T>T(これを理解する必要がない一般的な方法で宣言されたあなたのタイプのパラメータである)List<T>ですIEnumerable<T>)。
Jeppe Stig Nielsen

@ReginaldBlue 100%、同じものを投稿する予定でした。同様の答えは、重複した質問をすることはありません。
UuDdLrLrSs

回答:


218

エラーメッセージは不十分な情報であり、それは私のせいです。申し訳ありません。

発生している問題は、共分散が参照型でのみ機能するという事実の結果です。

あなたはたぶんIA今「参照型です」と言っているでしょう。はい、そうです。しかし、それT がに等しい言いませんでしIA。それT実装 する型でIAあり、値型はインターフェースを実装することができます。したがって、共分散が機能するかどうかはわかりません。

共分散が機能するようにするには、型パラメーターがclass制約とIAインターフェイス制約のある参照型であることをコンパイラーに通知する必要があります。

共分散には参照型の正当性の保証が必要であるため、エラーメッセージは変換が不可能であることを本当に伝える必要があります。これは基本的な問題だからです。


3
なぜあなたはそれがあなたのせいだと言ったのですか?
user4951 2018年

77
@ user4951:エラーメッセージを含むすべての変換チェックロジックを実装したため。
Eric Lippert、2018年

@BurnsBAこれは因果的な意味での単なる「障害」です。技術的には実装とエラーメッセージは完全に正しいものです。(それは、変換不能のエラーステートメントが実際の理由を詳しく説明できるということです。しかし、ジェネリックで適切なエラーを生成するのは難しいです。数年前のC ++テンプレートエラーメッセージと比較すると、これは明快で簡潔です。)
Peter-Reinstate Monica

3
@ PeterA.Schneider:それはありがたいです。しかし、Roslynでエラー報告ロジックを設計するための私の主な目標の1つは、特に、違反したルールだけでなく、可能な場合は「根本原因」を特定することです。たとえば、エラーメッセージは何のためのものcustomers.Select(c=>c.FristName)ですか?C#仕様では、これがオーバーロード解決エラーであることは非常に明確です。そのラムダを空にすることができるSelectという名前の適用可能なメソッドのセットが空です。しかし、根本的な原因はFirstNameタイプミスがあることです。
Eric Lippert

3
@ PeterA.Schneider:ジェネリック型の推論とラムダを含むシナリオが適切なヒューリスティックを使用して、どのエラーメッセージが開発者に最も役立つ可能性が高いかを推測するために、多くの作業を行いました。しかし、変換エラーメッセージについては、特に分散が関係している場合は、あまりうまくいきませんでした。私はいつもそれを後悔しました。
Eric Lippert、2018年

26

私は、Ericの優れたインサイダーの回答を、一般的な制約にあまり馴染みがないかもしれない人のためのコード例で補足したかっただけです。

次のSomethingようにシグネチャを変更しますclass制約が最初に来る必要があります。

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

2
私は興味があります...順序付けの重要性の背後にある理由は正確に何ですか?
トム・ライト

5
@TomWright-もちろん、仕様には多くの「理由」に対する回答が含まれていません。質問ですが、この場合は3つの異なるタイプの制約があることは明らかであり、3つすべてを使用する場合は具体的に指定する必要がありますprimary_constraint ',' secondary_constraints ',' constructor_constraint
Damien_The_Unbeliever

2
@TomWright:ダミアンは正しいです。パーサーの作者の利便性以外に、私が知っている特別な理由はありません。私は私のdruthersを持っていた場合は型制約の構文はかなりになり、より詳細な。class「クラス」ではなく「参照タイプ」を意味するため、これは悪いことです。私は冗長のようなもので幸せだっただろうwhere T is not struct
エリックリペット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.