「out」キーワードが2つの一見異なるコンテキストで使用されるのはなぜですか?


11

C#では、outキーワードは2つの異なる方法で使用できます。

  1. 引数が参照によって渡されるパラメーター修飾子として

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
    
  2. 共分散を指定する型パラメーター修飾子として

    // Covariant interface. 
    interface ICovariant<out R> { }
    
    // Extending covariant interface. 
    interface IExtCovariant<out R> : ICovariant<R> { }
    
    // Implementing covariant interface. 
    class Sample<R> : ICovariant<R> { }
    
    class Program
    {
        static void Test()
        {
            ICovariant<Object> iobj = new Sample<Object>();
            ICovariant<String> istr = new Sample<String>();
    
            // You can assign istr to iobj because 
            // the ICovariant interface is covariant.
            iobj = istr;
        }
    }
    

私の質問は:なぜですか?

初心者にとっては、この2つの接続は直感的に思えません。ジェネリックでの使用は、参照渡しとは何の関係もないようです。

out参照による引数の受け渡しに関連することを最初に学びましたが、これはジェネリックとの共分散を定義する使用法の理解を妨げました。

私が見逃しているこれらの用途の間に接続はありますか?


5
System.Func<in T, out TResult>delegateの共分散と反分散の使用法を見ると、接続が少しわかりやすくなります。
ルウォン

4
また、ほとんどの言語設計者は、キーワードの数を最小限にしようとすると、大きなコードベースでいくつかの既存の言語で新しいキーワードを追加すると痛い(名前とその単語を使用して、いくつかの既存のコードとの重複の可能性)である
バジーレStarynkevitch

回答:


20

接続はありますが、少しゆるいです。C#では、キーワード「in」および「out」はその名前が示すとおり、入力と出力を表します。これは、出力パラメーターの場合は非常に明確ですが、テンプレートパラメーターで何をする必要があるかはあまりわかりません。

リスコフ置換の原理を見てみましょう。

...

Liskovの原則は、新しいオブジェクト指向プログラミング言語で採用されている署名にいくつかの標準要件を課します(通常は型ではなくクラスのレベル。区別については、名義対構造のサブタイプを参照してください)。

  • サブタイプのメソッド引数の反分散。
  • サブタイプの戻り値の型の共分散。

...

反分散が入力に関連付けられ、共分散が出力に関連付けられている方法を参照してください。C#では、テンプレート変数にフラグoutを付けて共変にしますが、言及された型パラメーターが出力(関数の戻り値の型)としてのみ表示される場合にのみできることに注意してください。したがって、次は無効です。

interface I<out T>
{
  void func(T t); //Invalid variance: The type parameter 'T' must be
                  //contravariantly valid on 'I<T>.func(T)'.
                  //'T' is covariant.

}

同様に、で型パラメーターにフラグを立てた場合in、それは入力(関数パラメーター)としてのみ使用できることを意味します。したがって、次は無効です。

interface I<in T>
{
  T func(); //Invalid variance: The type parameter 'T' must
            //be covariantly valid on 'I<T>.func()'. 
            //'T' is contravariant.

}

要約すると、outキーワードとの関係は、関数パラメーターでは出力パラメーターであることを意味し、タイプパラメーターでは出力コンテキストでのみタイプが使用されることを意味します。

System.Funcまた、rwongが彼のコメントで言及した良い例です。内のSystem.Funcすべての入力パラメータとflaged AR in、及び出力パラメータを用いてflagedれますout。その理由はまさに私が説明したことです。


2
いい答え!ちょっと助かりました…それを待ちます…タイピング!ところで、あなたが引用したLSPの部分は、リスコフよりずっと前に実際に知られていました。これは、関数型の標準のサブタイプ規則です。(パラメーターの型は反変であり、戻り値の型は共変です)。リスコフのアプローチの新規性は、ルールをフレージング)でない共/ contravarianceの観点からではなくbehavorial事前/事後条件によって定義されるような代替(a)およびb)の点で履歴ルール全てこの推論を適用することが可能となり、これまで不可能だった可変データ型へ。
ヨルグW

10

@Gáborはすでに接続(「入ってくる」すべての共分散、「出る」すべての共分散)を説明しましたが、なぜキーワードを再利用するのですか?

まあ、キーワードは非常に高価です。これらをプログラムの識別子として使用することはできません。しかし、英語には非常に多くの単語があります。そのため、競合が発生する場合があり、変数とメソッド、フィールド、プロパティ、クラス、インターフェイス、または構造体の名前を不当に変更して、キーワードと競合しないようにする必要があります。たとえば、学校をモデル化する場合、クラスとは何ですか?classキーワードなので、クラスと呼ぶことはできません!

言語にキーワードを追加すると、さらに費用がかかります。基本的に、このキーワードを識別子として使用するすべてのコードを違法にし、あらゆる場所で後方互換性を破壊します。

inそしてout、彼らはちょうど再利用できるようなキーワードは、既に、存在していました。

彼らは可能性が型パラメータリストのコンテキスト内でのみキーワードである文脈のキーワードを追加したが、何のキーワードは、彼らが選んだのでしょうか?covariantそしてcontravariant+そして、-(Scalaは例えば、行ったように)?superそしてextends、Javaのように?どのパラメーターが共変と反変であるかを頭の中で覚えていますか?

現在のソリューションには、ニーモニックがあります。出力型パラメーターはoutキーワードを取得し、入力型パラメーターはinキーワードを取得します。メソッドパラメーターとの対称性に注意してください。出力パラメーターはoutキーワードを取得し、入力パラメーターはinキーワードを取得します(実際、入力はデフォルトなのでキーワードはありませんが、アイデアは得られます)。

[注:編集履歴を見ると、導入文で実際に2つを実際に切り替えていることがわかります。その間、私も賛成票を得ました!これは、そのニーモニックが実際にどれほど重要かを示すためのものです。]


共分散と反分散を覚える方法は、インターフェイス内の関数がジェネリックインターフェイスタイプのパラメータを取る場合に何が起こるかを考慮することです。一つは持っている場合interface Accepter<in T> { void Accept(T it);};Accepter<Foo<T>>受け入れるT場合は、入力パラメータとしてFoo<T>出力パラメータ、およびその逆として受け付けます。したがって、分散。対照的に、interface ISupplier<out T> { T get();};a Supplier<Foo<T>>はどんな種類の分散をFoo持っています-つまり分散です。
supercat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.