web-dev-qa-db-ja.com

共変性の説明

まず、SOと共変性と反変性についてのブログで多くの説明を読みました。そして、 Eric Lippert でこのような素晴らしいシリーズを作成してくれたことに感謝します。 共分散と反変性

しかし、私は少し頭を動かそうとしているというより具体的な質問があります。

Ericの説明 ごとに私が理解している限り、共変性と反変性はどちらも変換を表す形容詞です。共変変換は型の順序を保持する変換であり、共変変換はそれを逆にする変換です。

私は、ほとんどの開発者が直感的に理解できるような方法で共分散を理解しています。

//covariant operation
Animal someAnimal = new Giraffe(); 
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal(); 

ここでの戻り操作は、両方の動物が哺乳類またはキリンよりもまだ大きいサイズを維持しているため、共変です。その点で、ほとんどの戻り演算は共変であり、反変演算は意味がありません。

  //if return operations were contravariant
  //the following would be illegal
  //as Mammal would need to be stored in something
  //equal to or less derived than Mammal
  //which would mean that Animal is now less than or equal than Mammal
  //therefore reversing the relationship
  Animal someAnimal =  Mammal.GetSomeMammal(); 

もちろん、このコードはほとんどの開発者にとって意味がありません。

私の混乱は、共変性の引数パラメーターにあります。次のような方法があった場合

bool Compare(Mammal mammal1, Mammal mammal2);

私は常に、入力パラメーターが常に反変の振る舞いを強制することを学びました。タイプが入力パラメーターとして使用される場合、その動作は反変である必要があります。

ただし、次のコードの違いは何ですか

Mammal mammal1 = new Giraffe(); //covariant
Mammal mammal2 = new Dolphin(); //covariant

Compare(mammal1, mammal2); //covariant or contravariant?
//or
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant?

あなたがこのようなことをすることができないのと同じトークンであなたはすることができません

   //not valid
   Mammal mammal1 = new Animal();

   //not valid
   Compare(new Animal(), new Dolphin());

私が求めているのは、メソッドの引数が共変変換を渡す理由だと思います。

長い投稿でごめんなさい、多分私はこれを間違って理解しています。

編集:

以下のいくつかの会話によると、たとえばデリゲートレイヤーを使用すると、明らかに共変性を示すことができることを理解しています。次の例を考えてみましょう

//legal, covariance
Mammal someMammal = new Mammal();
Animal someAnimal = someMammal;

// legal in C# 4.0, covariance (because defined in Interface)
IEnumerable<Mammal> mammalList = Enumerable.Empty<Mammal>();
IEnumerable<Animal> animalList = mammalList;

//because of this, one would assume
//that the following line is legal as well

void ProcessMammal(Mammal someMammal);

Action<Mammal> processMethod = ProcessMammal;
Action<Animal> someAction = processMethod;

もちろん、これは違法です。なぜなら、誰かが任意の動物をsomeActionに渡すことができるのに対し、ProcessMammalは、哺乳類またはより具体的なもの(哺乳類よりも小さい)を期待しているからです。そのため、someActionはアクションまたはより具体的なもののみである必要があります(アクション)

ただし、これは中央にデリゲートのレイヤーを導入しています。反変投影が発生するためには、中央にデリゲートが存在する必要がありますか?また、Processをインターフェイスとして定義する場合、上記で示したデリゲートを誰かに実行させたくないという理由だけで、引数パラメーターを反変型として宣言しますか?

public interface IProcess<out T>
{
    void Process(T val);
}
36
Stan R.

更新:おっと。結局のところ、最初の回答で差異と「割り当ての互換性」を混同しました。それに応じて答えを編集しました。また、私はそのような質問にもっとよく答えるべきだと思うブログ投稿を書きました: 共変性と反変性のFAQ

回答:最初の質問に対する答えは、この例では共変性がないということだと思います。

bool Compare(Mammal mammal1, Mammal mammal2); 
Mammal mammal1 = new Giraffe(); //covariant - no             
Mammal mammal2 = new Dolphin(); //covariant - no            

Compare(mammal1, mammal2); //covariant or contravariant? - neither            
//or             
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? - neither

さらに、ここには共分散さえありません。あなたが持っているものは「割り当て互換性」と呼ばれます。これは、より派生したタイプのインスタンスを、より派生していないタイプのインスタンスにいつでも割り当てることができることを意味します。

C#では、分散は配列、デリゲート、および汎用インターフェイスでサポートされています。 Eric Lippertがブログ投稿で述べたように 共分散と割り当ての互換性の違いは何ですか? 分散を型の「射影」と考える方がよいということです。

共分散は、割り当ての互換性ルールに従うため、理解しやすくなります(派生型の多い配列は、派生型の少ない配列 "object [] objs = new string [10];"に割り当てることができます)。共変性はこれらの規則を逆転させます。たとえば、「string [] string = newobject [10];」のようなことができると想像してください。もちろん、明らかな理由でこれを行うことはできません。しかし、それは反変性になります(ただし、配列は反変ではなく、共分散のみをサポートします)。

これがMSDNの例で、共変性が実際に何を意味するのかを示してくれることを願っています(私は現在これらのドキュメントを所有しているので、ドキュメントで不明な点がある場合は、遠慮なくフィードバックをお寄せください)。

  1. ジェネリックコレクションのインターフェイスでの分散の使用

    Employee[] employees = new Employee[3];
    // You can pass PersonComparer, 
    // which implements IEqualityComparer<Person>,
    // although the method expects IEqualityComparer<Employee>.
    IEnumerable<Employee> noduplicates =
        employees.Distinct<Employee>(new PersonComparer());
    
  2. デリゲートでの分散の使用

    // Event hander that accepts a parameter of the EventArgs type.
    private void MultiHandler(object sender, System.EventArgs e)
    {
       label1.Text = System.DateTime.Now.ToString();
    }
    public Form1()
    {
        InitializeComponent();
        // You can use a method that has an EventArgs parameter,
        // although the event expects the KeyEventArgs parameter.
        this.button1.KeyDown += this.MultiHandler;
        // You can use the same method 
        // for an event that expects the MouseEventArgs parameter.
        this.button1.MouseClick += this.MultiHandler;
     }
    
  3. FuncおよびAction Generic Delegatesの分散の使用

     static void AddToContacts(Person person)
     {
       // This method adds a Person object
       // to a contact list.
     }
    
     // The Action delegate expects 
     // a method that has an Employee parameter,
     // but you can assign it a method that has a Person parameter
     // because Employee derives from Person.
     Action<Employee> addEmployeeToContacts = AddToContacts;
    

お役に立てれば。

27

私の理解では、共変性/反変であるのはサブタイプの関係ではなく、それらのタイプ(デリゲートやジェネリックなど)間の操作(または予測)です。したがって:

Animal someAnimal = new Giraffe();

は共変ではありませんが、キリン型は動物型よりも「小さい」ため、これは単なる割り当ての互換性です。次のように、これらのタイプ間に何らかの予測がある場合、共分散/逆分散が問題になります。

IEnumerable<Giraffe> giraffes = new[] { new Giraffe() };
IEnumerable<Animal> animals = giraffes;

これはC#3では無効ですが、キリンのシーケンスは動物のシーケンスであるため、可能であるはずです。射影T -> IEnumerable<T>は、Giraffe < AnimalIEnumerable<Giraffe> < IEnumerable<Animal>であるため、型の関係の「方向」を保持します(代入では、左側の型が少なくとも右側と同じ幅である必要があることに注意してください) )。

逆分散は、型の関係を逆転させます。

Action<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)};
Action<Giraffe> printGiraffe = printAnimal;

これもC#3では合法ではありませんが、動物をとる行動はキリンの通過に対処できるためです。ただし、Giraffe < AnimalおよびAction<Animal> < Action<Giraffe>以降、射影は型の関係を逆転させました。これはC#4では合法です。

したがって、あなたの例の質問に答えるには:

//the following are neither covariant or contravariant - since there is no projection this is just assignment compatibility
Mammal mammal1 = new Giraffe();
Mammal mammal2 = new Dolphin();

//compare is contravariant with respect to its arguments - 
//the delegate assignment is legal in C#4 but not in C#3
Func<Mammal, Mammal, bool> compare = (m1, m2) => //whatever
Func<Giraffe, Dolphin, bool> c2 = compare;

//always invalid - right hand side must be smaller or equal to left hand side
Mammal mammal1 = new Animal();

//not valid for same reason - animal cannot be assigned to Mammal
Compare(new Animal(), new Dolphin());
10
Lee

このように見てください:Mammal m = Func(g(Mammal))の形式のサブタイプMammalを扱う関数funcがある場合、Mammalを次のように交換できますが含むもの哺乳類、これがベース動物

下の画像を理解するためのスポーツの例えとして、クリケットのように素手でボールをキャッチすることができますが、野球のグローブを使用してボールをキャッチすることも可能です(そして簡単です)。

左側に表示されているのは共分散であり、パラメーター部分の内部に表示されているのは反変性です。

enter image description here

「なぜ左の緑の曲線が赤の曲線よりも大きいのか?通常はベースタイプよりも多くのことを行うサブタイプは、より大きくなるはずではないのか?」と疑問に思うかもしれません。回答:いいえ。括弧のサイズは、ベン図のように、許可されるさまざまなオブジェクトを示します。哺乳類のセットは、動物のセットよりも小さいです。同様に、f(Mammal)はf(Animal)よりも小さいオブジェクトのセットのみをサポートするためです(つまり、哺乳類を処理する関数はtはすべての動物を処理しますが、動物を処理する関数は常に哺乳類を処理できます。したがって、f(animal)はf(mammal)これにより、逆変になります。

1
arviman

(コメントに応じて編集)

このトピックに関するMSDNの記事 関数をデリゲートに一致させる場合に適用される共変性と反変性について説明しました。デリゲート型の変数:

public delegate bool Compare(Giraffe giraffe, Dolphin dolphin);

(共変性のために)次の関数を入力できます:

public bool Compare(Mammal mammal1, Mammal mammal2)
{
    return String.Compare(mammal1.Name, mammal2.Name) == 0;
}

私の読書から、それは関数を直接呼び出すこととは関係ありませんが、関数をデリゲートと照合することと関係があります。個々の変数またはオブジェクトの割り当てが反変または共変であるため、それがあなたが示すレベルまで要約できるかどうかはわかりません。しかし、代理人の割り当ては、リンクされた記事によると、私にとって意味のある方法で反変性または共分散を使用します。デリゲートの署名には実際のインスタンスよりも多くの派生型が含まれているため、これは「共変性」と呼ばれ、デリゲートの戻り値の型が実際のインスタンスよりも派生が少ない「共分散」とは別のものです。

1
BlueMonkMN