38

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

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

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

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

//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がActionまたはより具体的なもののみである必要がある理由です(アクション)

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

public interface IProcess<out T>
{
    void Process(T val);
}
4

5 に答える 5

28

更新:おっと。結局のところ、最初の回答で分散と「割り当ての互換性」を混同しました。それに応じて回答を編集しました。また、そのような質問にもっとうまく答えられるようにブログ記事を書きました: Covariance and Contravariance 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[] strings = new object[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 ジェネリック デリゲートのバリアンスの使用

     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;
    

お役に立てれば。

于 2009-12-30T00:15:04.023 に答える
16

共分散と反分散は、クラスをインスタンス化するときに観察できるものではありません。したがって、あなたの例のように、単純なクラスのインスタンス化を見るときにそれらの1つについて話すのは間違っています: Animal someAnimal = new Giraffe(); //covariant operation

これらの用語は操作を分類しません。共分散、反分散、不変という用語は、クラスの特定の側面とそのサブクラスの間の関係を表します。

共分散
継承の方向と同様にアスペクトが変化することを意味します。
反変性
相が継承の方向とは逆に変化することを意味します。
不変性
アスペクトがクラスからそのサブクラスに変更されないことを意味します。

Cov.、Contrav.について話すとき、私たちは一般的に次の側面を考慮します。および投資家:

  • メソッド
    • パラメータの種類
    • 戻り値の型
    • スローされた例外などのその他の署名関連の側面。
  • ジェネリック

用語をよりよく理解するために、いくつかの例を見てみましょう。

class T
class T2 extends T
 
//Covariance: The return types of the method "method" have the same
//direction of inheritance as the classes A and B.
class A { T method() }
class B extends A { T2 method() }
 
//Contravariance: The parameter types of the method "method" have a
//direction of inheritance opposite to the one of the classes A and B.
class A { method(T2 t) }
class B { method(T t) }
どちらの場合も、「メソッド」はオーバーライドされます! さらに、上記の例は、Cov の唯一の合法的な発生です。とコントラヴ。オブジェクト指向言語で.:

  • 共分散 - 戻り値の型と例外スロー ステートメント
  • 反変性 - 入力パラメーター
  • 不変性 - 入力および出力パラメーター

上記のリストをよりよく理解するために、いくつかの反例を見てみましょう。

//Covariance of return types: OK
class Monkey { Monkey clone() }
class Human extends Monkey { Human clone() }
 
Monkey m = new Human();
Monkey m2 = m.clone(); //You get a Human instance, which is ok,
                       //since a Human is-a Monkey.
 
//Contravariance of return types: NOT OK
class Fruit
class Orange extends Fruit
 
class KitchenRobot { Orange make() }
class Mixer extends KitchenRobot { Fruit make() }
 
KitchenRobot kr = new Mixer();
Orange o = kr.make(); //Orange expected, but got a fruit (too general!)
 
//Contravariance of parameter types: OK
class Food
class FastFood extends Food
 
class Person { eat(FastFood food) }
class FatPerson extends Person { eat(Food food) }
 
Person p = new FatPerson();
p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats.
 
//Covariance of parameter types: NOT OK
class Person { eat(Food food) }
class FatPerson extends Person { eat(FastFood food) }
 
Person p = new FatPerson();
p.eat(new Food()); //Oops! FastFood expected, but got Food (too general).

このトピックは非常に洗練されているため、非常に長く続けることができます。Covを確認することをお勧めします。とコントラヴ。自分でジェネリックの。さらに、例を完全に理解するには、動的バインディングがどのように機能するかを知る必要があります (どのメソッドが正確に呼び出されるか)。

これらの用語は、データ型を別の型のサブ型としてモデル化するために必要な基準を定義する Liskov 置換原則から生じました。また、それを調査することもできます。

于 2009-12-30T03:02:27.573 に答える
10

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

Animal someAnimal = new Giraffe();

共変ではありませんが、タイプ Giraffe はタイプ Animal より「小さい」ため、これは単なる代入の互換性です。次のように、これらのタイプ間に投影がある場合、共分散/反分散が問題になります。

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

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

反分散は、型の関係を逆にします。

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

これも C#3 では合法ではありませんが、動物を捕まえるアクションはキリンを通過しても対処できるため、合法であるべきです。ただし、Giraffe < AnimalAction<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());
于 2009-12-26T15:15:00.843 に答える
1

(コメントに応じて編集されました)

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

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

(反変性のため)次の関数を入力できます。

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

私の読書から、関数を直接呼び出す必要はありませんが、関数をデリゲートと一致させる必要はありません。個々の変数またはオブジェクトの割り当てが反変または共変であり、あなたが示すレベルまで煮詰めることができるかどうかはわかりません。しかし、デリゲートの割り当ては、リンクされた記事によると、私にとって意味のある方法で反変性または共変性を使用します。デリゲートのシグネチャには実際のインスタンスよりも多くの派生型が含まれているため、これは "反変性" と呼ばれ、デリゲートの戻り値の型が実際のインスタンスよりも派生されていない "共変性" とは別のものです。

于 2009-12-26T13:56:05.030 に答える