いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

C#4.0の追加機能-06(共変性と反変性, Covariance and Contravariance, Generic Interface, ジェネリックインターフェース)


以下、自分用のメモです。


C# 4.0では、ジェネリックインターフェースに対して、「共変性(Covariance)」と「反変性(Contravariance)」
が適用されるようになりました。


言葉がややこしいので、実際にソースを見た方が分かりやすいです。


共変性とは、簡単に言うと、子のクラスの型を親クラスの型に変換出来ること。

string str = "gsf_zero1";
object obj = str;

これは、利用している人が多いと思います。


C# 4.0より、ジェネリックインターフェースに対して、共変性を利用出来るようになりました。

IEnumerable<string> strings = new []{ "gsf_zero1", "gsf_zero2" };
IEnumerable<object> objects = strings;


共変性は、型引数に対して「out」キーワードを利用して、表明します。
.NET 4.0では、IEnumerableは以下のように定義されています。

public interface IEnumerable<out T> : IEnumerable { ... }

「out」キーワードが付与されている場合、その型引数は「出力方向」にしか利用できなくなります。
出力方向とは、つまり戻り値としてのみ利用するという意味です。
この制限を付ける事で、共変性が実現されます。


反変性は、共変性の逆です。
簡単に言うと、親クラスの型を子クラスの型に設定出来ること。

class Parent         { ... }
class Child : Parent { ... }

delegate Parent SampleDelegate();

Child SampleMethod() { ... }

static void Main()
{
    SampleDelegate theDelegate = SampleMethod;
    theDelegate();    
}


C# 4.0より、ジェネリックインターフェースに対して、反変性を利用出来るようになりました。

Action<object> objAction = x => Console.WriteLine(x);
Action<string> strAction = objAction;

strAction("gsf_zero1");


反変性は、型引数に対して「in」キーワードを利用して、表明します。
.NET 4.0では、Actionは以下のように定義されています。

public delegate void Action<in T>(T obj)

「in」キーワードが付与されている場合、その型引数は「入力方向」にしか利用できなくなります。
入力方向とは、つまり引数としてのみ利用するという意味です。
この制限を付ける事で、反変性が実現されます。



以下、サンプルです。

    #region CovarianceSamples-01
    public class CovarianceSamples01 : IExecutable
    {
        public void Execute()
        {
            //
            // Covariance(共変性)は、簡単に言うと、子のオブジェクトを親の型として扱う事。
            //
            // 例:
            //     string str = "gsf_zero1";
            //     object obj = str;
            //
            // C# 4.0では、この概念をジェネリックインターフェースに対して適用できるようになった。
            // 共変性を表明するには、型引数を定義する際に、「out」キーワードを設定する。
            //
            // .NET 4.0では、IEnumerable<T>は以下のように定義されている。
            //     public interface IEnumerable<out T> : IEnumerable { ... }
            //
            // 「out」キーワードは、この型引数を「出力方向」にしか利用しないことを表明している。
            // つまり、「out」キーワードが付与されるとTを戻り値などの出力値にしか利用できなくなる。
            // (outを指定している状態で、入力方向、つまりメソッドの引数などにTを設定しようとすると
            //  コンパイルエラーが発生する。)
            //
            // 出力方向にしか利用しないので、子の型(つまり狭義の型)を親の型(つまり広義の型)に
            // 設定しても、問題ない。
            //    「内部の型はstringであるが、実際に値を取り出す際には親の型で受け取るので問題ない」
            //
            // Contravariance(反変性)は、この逆を行うものとなる。
            //
            IEnumerable<string> strings = new []{ "gsf_zero1", "gsf_zero2" };
            IEnumerable<object> objects = strings;
            
            foreach (var obj in objects)
            {
                Console.WriteLine("VALUE={0}, TYPE={1}", obj, obj.GetType().Name);
            }
        }
    }
    #endregion
    
    #region ContravarianceSamples-01
    public class ContravarianceSamples01 : IExecutable
    {
        public void Execute()
        {
            //
            // Contravariance(反変性)は、簡単に言うと、親のオブジェクトを子の型として扱う事。
            // (共変性の逆です。)
            //
            // 例:
            //     class Parent         { ... }
            //     class Child : Parent { ... }
            //
            //     delegate Parent SampleDelegate();
            //
            //     Child SampleMethod() { ... }
            //
            //     // ここで反変性が発生している。
            //     SampleDelegate theDelegate = SampleMethod;
            //
            // C# 4.0では、この概念をジェネリックインターフェースに対して適用できるようになった。
            // 共変性を表明するには、型引数を定義する際に、「in」キーワードを設定する。
            //
            // .NET 4.0では、Action<T>は以下のように定義されている。
            //     public delegate void Action<in T>(T obj)
            //
            // 「in」キーワードは、この型引数を「入力方向」にしか利用しないことを表明している。
            // つまり、「in」キーワードが付与されるとTを引数などの入力値にしか利用できなくなる。
            // (inを指定している状態で、出力方向、つまりメソッドの戻り値などにTを設定しようとすると
            //  コンパイルエラーが発生する。)
            //
            // 入力方向にしか利用しないので、親の型(つまり広義の型)を子の型(つまり狭義の型)に
            // 設定しても、問題ない。
            //    「外部の型はstringであるが、実際にデータが渡される際、内部の引数の型はobjectなので問題ない」
            //
            // 例:
            //     Action<object> objAction = x => Console.WriteLine(x);
            //     Action<string> strAction = objAction;
            //
            //     strAction("gsf_zero1");
            //
            //     上記の例だと、objActionをstrActionに設定している。つまり親クラスの型で定義されているAction<object>を
            //     子のクラスのAction<string>に設定している。
            //     その後、strAction("gsf_zero1")としているので、外部から渡された値はstring型である。
            //     しかし、objActionの引数の型は、親クラスであるobject型なので問題なく動作する。
            //     (親クラスに定義されている振る舞いしか利用できないため。)
            //
            // Covariance(共変性)は、この逆を行うものとなる。
            //
            Action<object> objAction = x => Console.WriteLine(x);
            Action<string> strAction = objAction;
            
            strAction("gsf_zero1");
        }
    }
    #endregion


便利だな〜と思う反面、私自身で言えば、自分で定義したクラスに共変性とか反変性を付与することは
ほぼ無いと思ってたり・・・・。