9. ボックス化およびボックス化解除を最小限に抑える。

(そのほかの参考:https://ufcpp.net/study/csharp/rmboxing.html

値型は多態性を持たない型。 一方で.NetではSystem.Objectというすべてのオブジェクトの親である参照型を定義している。 このギャップをうめるために、ボックス化とボックス化解除を用意している。

  • ボックス化:値型を不定な参照型オブジェクトのメンバとすることで参照型が必要な場面においても値型を利用できる仕組み

  • ボックス化解除:ボックス化された値型のコピーを取り出すこと。

  • System.Objectやインタフェースを必要とする場面で値型を使用する場合に利用する。常にパフォーマンスを落とす。また、ボックス化(解除)は常にコピーは一時的なコピーを作るため潜在的なバグの温床。

  • ボックスはヒープ上に確保される。(ヒープ、スタック、領域についてどこかで整理しておく必要あり。)

  • ボックスに格納された値型を参照する場合、格納庫された値型のコピーが生成されて返される。ボックスの中身にアクセス(参照)する際は毎回コピーされた新しい値が返される。

  • 名前のない参照型が作成される。(値型をSystem.Objectbの参照に変換するため)

例えば、

int x = 5;
object y = x; //box
int z = (int)y; //unbox

あるいは、

Console.WriteLine($"first {firstNumber}$);

においては、

int i=25;
object o=j;
Console.WriteLine(o.ToString());

が起きている。

一般的にヒープ領域確保はスタックと比べると重たい処理である。値型の利点はスタック上に値を置く。(ヒープを使わないことによる性能向上である)

ボックス化を避けるためには 具体的な方をできる限り指定する。

class Program
{
    static void Main()
    {
        ObjectWriteLine(5);
        IntWriteLine(5);
    }

    static void ObjectWriteLine(object x)
    {
        // object.ToString が呼ばれる
        // 値型に対してはボックス化が必要
        Console.WriteLine(x.ToString());
    }

    static void IntWriteLine(int x)
    {
        // こういう場合は、int.ToString が直接呼ばれる
        // virtual メソッドだからといって、必ず virtual に呼ばれるわけじゃない
        // コンパイルの時点で型が確定してるなら、非 virtual にメソッドを呼ぶ
        Console.WriteLine(x.ToString());
    }
}

int.ToString(int側でオーバーライドしたもの)が直接呼ばれるため、ボックス化する必要はない。一方で引数がオブジェクトの時ボックス化が起きる。

もう一つの例:

Console.WriteLine($@"数値 {firstnumber.ToString()}");

あらかじめ文字列インスタンスに変換されている。 暗黙的にSystem.Objectへの変換が行われることを注意するべき。

  • 構造体は値型。これのコレクションはコピーができてしまう。不変な値型を作成するべき。

  • ジェネリック型について: