ジェネリックを勉強していると、共変性と反変性ってでてきます。
ググればググるほどよくわからなくなるのですが、次ような解釈で問題ないでしょうか?
詳しい方いらっしゃいましたら教えて頂けないでしょうか?
(以下、自分なりの解釈)
ジェネリック型は、さまざまなデータ型に対して、同じ機能を実行させるために必要な処理を行うプログラミング機能を提供します。ジェネリック型のメソッドは、呼び出し側が提供するデータ型に合わせてデータ型を指定してインスタンスを作成して使用することになります。その際、完全にデータ型を合致させることが望ましいのですが、場合によってが、融通性を持たせたい場合が発生します。例えば、List<String>型データとArray型のデータに対して同じ機能を実行いたい場合、List<String>型とArray型に対するジェネリック型インスタンスをそれぞれ作って使用すればいいことになります。しかし、ジェネリック型メソッドがデータソースに求める機能が列挙をベースとしたものであれば、IEnumerable<String>型で定義すれば、一つのデリゲート定義でList<String>型とArray型のデータソースが受け取れるという融通性を得ることができます。この際、呼び出し側のデータ型と受け取り側のデータ型が異なるため、データ変換処理が発生します。List<T>クラス、Arrayクラスは、共にIEnumerable<T>を実装していますので、IEnumerable<T>と比べて強い派生型(狭い型)と言えます。即ち、強い派生型から弱い派生型への型変換がなされたということになります。その際に、データとしての互換性がある状況の場合を反変性があると言います。同様な状況は、メソッドからの返り値の受け渡しでも発生し、返り値の場合は、逆に弱い派生型から強い派生型へ変換する必要があり共変性といいます。次の例は、List(Of Integer)型,Array型をIEnumerable(Of Integer)型として受け取り、なんらかの処理をした後、IEnumerable(Of Integer)型を返し、List(Of Integer)型、Array型として受け取った。というものです。
(サンプルコード)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim myList As New List(Of Integer)(New Integer() {1, 2, 3, 4, 5})
Console.WriteLine("渡したデータ型は、{0}", myList.GetType)
Dim RetList = Enume(myList)
Console.WriteLine("返されたデータ型は、{0}", RetList.GetType)
Console.WriteLine("---------")
Dim myArray() As Integer = {1, 2, 3, 4, 5}
Console.WriteLine("渡したデータ型は、{0}", myArray.GetType)
Dim te2 = Enume(myArray)
Console.WriteLine("返されたデータ型は、{0}", te2.GetType)
End Sub
Function Enume(MyEnume As IEnumerable(Of Integer))
As IEnumerable(Of Integer)
Console.WriteLine("受け取ったデータ型は、{0}", MyEnume.GetType)
'何らかの処理をしたと仮定
Return MyEnume
End Function
解釈の内容が正しいのかと、サンプルとして適切かどうか?
教えて頂けないでしょうか?
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim x() As List(Of Button) = Nothing 'Dim y() As List(Of Control) = x 'これは NG(コンパイルエラー)
Dim b() As Button = {Button1} Dim c() As Control = b '歴史的な事情から、配列は共変となっています
'このような場合、書き込み(反変)は本来 NG ですが、コンパイルできてしまいます。 'そのため、実行時に ArrayTypeMismatchException 例外となってしまいます。 c(0) = Me '本来は List(Of ) 同様、コンパイル時に検出できるのが望ましい。 End Sub
ジェネリックの共変性と反変性について書かれた下記の記事でも、In や Out が出てきますね。 https://www.atmarkit.co.jp/fdotnet/chushin/vb2010features_01/vb2010features_01_03.html
提示頂いたサンプルにおいて、 IEnumerable(Of T) ジェネリック型インターフェイスが登場しています。 この型についてヘルプで調べてみると、VB2008 (.NET Framework 3.5) までは Public Interface IEnumerable(Of T) Implements IEnumerable という定義であると書かれていたのに対し、VB2010 (.NET Framework 4) からは Public Interface IEnumerable(Of Out T) Implements IEnumerable であることが分かるはずです。Out が付与されたことで、共変性を明示することができます。
一方、反変性の In については、Action(Of T) ジェネリック型デリゲートなどで使われています。 VB2008 付属の .NET Framework 3.5 ヘルプでは Public Delegate Sub Action(Of T) (obj As T) という定義だったものが、.NET Framework 4 からは Public Delegate Sub Action(Of In T)(obj As T) という定義に変わっていますね。
たとえば、 Dim txt As TextBox = Me.TextBox1 Dim item As Control = txt というコードが問題無いことは分かりますよね。 TextBox は Cotrol を継承していますので、問題なく代入できるわけです。
では何故、下記のコードはエラーになるのでしょう?
Public Class Form1 Private items As List(Of Control)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim textBoxes As New List(Of TextBox) textBoxes.Add(Me.TextBox1) textBoxes.Add(Me.TextBox2) textBoxes.Add(Me.TextBox3)
Public Class Form1 'Private items As List(Of Control) Private items As IEnumerable(Of Control)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim textBoxes As New List(Of TextBox) textBoxes.Add(Me.TextBox1) textBoxes.Add(Me.TextBox2) textBoxes.Add(Me.TextBox3)
Me.items = textBoxes 'IEnumerable(Of ) への代入は OK End Sub End Class
IEnumerable(Of T) インターフェイスでは、型パラメーターで指定された T 型もしくは T を継承した型を、「出力方向だけ」にしか使用しない仕様です。
そこでこのように、 『指定された型パラメーターが、出力(戻り値、ReadOnly Property)にしか使われない』 ことを明確に伝えられるよう、VB2010 からは Out キーワードをつけて宣言できるようになりました。
> Public Interface IEnumerable(Of Out T)
上記を踏まえて、先ほどの共変性と反変性の件と In / Out キーワードがどう関わってくるかを見てみます。
'=== 「In」なので「入力」にしか使われていない Public Interface ISample1(Of In T As Control) 'この型パラメータ T では In によって『共変性』が保証されており、 ' T 型は常に「広い型から狭い型へ変換するだけ」の実装になっている Sub Test(ByVal x As T) WriteOnly Property Prop As T 'たとえば Of Control で実装された場合に、上記メンバーに対して 'Label インスタンスや Form インスタンスを「入力」したとしても、何の問題も無い End Interface
'=== 「Out」なので「出力」にしか使われていない Public Interface ISample2(Of Out T As Control) 'この型パラメータ T では Out によって『反変性』が保証されており、 ' T 型は常に「狭い型から広い型へ変換するだけ」の実装になっている Function Test() As T ReadOnly Property Prop As T 'たとえば Of Control の場合に、上記メンバーの戻り値から 'Label インスタンスや Form インスタンスが「出力」されてきたとしても、何の問題も無い End Interface
'=== 双変の場合(つまり共変と反変の両方がありうる場合)は '=== In も Out も付与できない(付与するとコンパイルエラー) Public Interface ISample3(Of T As Control) Sub Test(ByRef x As T) End Interface Public Interface ISample4(Of T As Control) Property Prop As T End Interface
Public Class Form1 Private items As List(Of Control)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim textBoxes As New List(Of TextBox) textBoxes.Add(Me.TextBox1) textBoxes.Add(Me.TextBox2) textBoxes.Add(Me.TextBox3)