Archive | 13:54

ジェネリック

8 12月

(IL/バイト コードの)ポロリもあるよ!

 

本日はジェネリックに関する説明です。

ジェネリックとは

ジェネリック(英語だとgenericsです。訳すなら「総称」)は、型をパラメーター化する仕組みです。

操作対象となる引数やメンバーの型だけ差し替えて、全く同じ操作をしたい場合あがります。わかりやすい例は、コレクションでしょう。要素の型だけが違って、同じ操作(要素の追加、更新、参照、削除)を行いたいです。

var list = new List<int>();
list.Add(1);
var x = list[0];
Console.WriteLine(x);
//↑↓ 
要素の型だけが違う
var sList = new List<string>();
sList.Add(“abc”);
var s = sList[0];
Console.WriteLine(s);

この場合、Listクラスに対して、intstringという型をパラメーターとして渡しています。

要素の型(整数か、文字列か、クラスか、など)と格納方式(配列リストか、連結リストか、それとも、辞書(ハッシュ テーブル)か)を別々に考えることができます。

Javaのジェネリック

以下のようなJavaコードを見てみましょう。リストを作って要素を挿入・取得するだけのものです。

import java.util.*;

public class Program
{
public static void main (String[] args)
{
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
int x = list.get(0);
System.out.println(x);
}
}

コンパイル結果のバイト コード(Java仮想マシンの中間言語)は以下のようになります

型情報が失われます。Object型を格納するリストが作られて、キャストが挿入されます。

すなわち、以下のコードと同じ意味になります。

import java.util.*;

public class Program
{
public static void main (String[] args)
{
ArrayList list = new ArrayList();
list.add(Integer.valueOf(1));
int x = ((Integer)list.get(0)).intValue();
System.out.println(x);
}
}

 

上記JavaコードをProgram.javaという名前で保存して、

javac Program.java
javap –c Program

 

.NETのジェネリック

同じようなコードをC#で書いてみましょう。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var list = new List<int>();
        list.Add(1);
        var x = list[0];
        Console.WriteLine(x);
    }
}

コンパイル結果のIL(.NET仮想マシンの中間言語)は以下のようになります

.NETの仮想マシンは、ジェネリックに対応した命令を持っています。その結果、余計なキャストが不要になります。

Visual Studioと一緒に「IL 逆アセンブラー」というツールがインストールされます。

仮想マシン レベルでの対応の意義

上記の差を見ての通り、仮想マシン レベルでジェネリックに対応することで、以下の利点があります。

  • キャストが不要です
    • 特に、自動ボクシング(値型をobject化する)という、コストのかかる処理が不要になります
  • 型情報が失われません
    • 不正なキャストができなくなります
    • リフレクションでも型情報を取得できます

 

昨日説明したような、値型にしたい型(サイズが小さくて、読み書きの頻度が高い型)に対しては特に大きな差になります。