技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

.NET を理解する(基礎にして初歩にあらず)。

はじめに

.NET には様々な「型」が存在しており、これらの「型」を利用したり、ユーザが独自に「型」を定義することで処理の責任範囲や処理の種類を分類します。
オブジェクト指向に関する哲学や考え方を紹介する訳ではありませんので、ここでは説明しませんが、気になる方は Wikipedia を参照するとよいでしょう。
この文章では、C++ / Java 型のオブジェクト指向を理解している事、及び、int , bool 等の基本型やメソッドを理解している事を前提とします。

http://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91

基本の型

.NET の型は、CTS (CommonTypeSystem)で定義され、大きく二つの種類に分類されます。値型 と 参照型 です。
Java でも同様の仕組みがありますが、実行速度を稼ぐ目的で存在しています。

値型には、数値、ブール値、構造体が含まれます。参照型は class 宣言された全ての型で、名前の通り参照と実体が別々に存在します。
利用的な違いでは、null を許容するか、初期値に値を持つかの差があります。
例えば、以下のコードはコンパイルエラーとなります。

namespace SampleCode
{
    public struct UserInfo
    {
        public int Age
        {
            get;
            set;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            UserInfo azalea = null;
        }
    }
}

int , bool , DateTime 等でも同様で、値型はあくまで値を保持します。
その為、メソッド間での引数では、以下のような動作をします。

namespace SampleCode
{
    public struct UserInfo
    {
        public int Age
        {
            get;
            set;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            UserInfo azalea = new UserInfo();
            azalea.Age = 27;
            Sample(azalea);

            // 27 と表示されます。
            Console.Write(azalea.Age);
        }

        static void Sample(UserInfo info)
        {
            // 値のコピーが渡されるので、何を変更しても無駄。
            info.Age = 55;
        }
    }
}

メソッド引数では、値のコピーが渡されます。
その為、中でどれだけ弄っても、元の値は変更されません。


これに対し、参照型は、null を許容し、かつ引数では参照を渡します。

namespace SampleCode
{
    public class UserInfo
    {
        public int Age
        {
            get;
            set;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            UserInfo azalea = null;
            azalea = new UserInfo();
            azalea.Age = 27;
            Sample(azalea);

            // 55 と表示されます。
            Console.Write(azalea.Age);
        }

        static void Sample(UserInfo info)
        {
            // 参照先の Age を書き換えます。
            info.Age = 55;
        }
    }
}

この動作は、Java の参照の扱いや、C++ のポインタ引数と考えるとよいでしょう*1

値型に対し、null を許容したい場合、Nullable ジェネリッククラスを利用します。

Nullable<int> value = null;
// 以下の記述でも利用可能
// int? value = null;

内部的な実装では、値型と参照型の実体はそれぞれ違うメモリに格納されるという事もあります。
値型は、基本的にスタックメモリに積まれて動作します。その為、アクセスが非常に高速です。
メソッド間の呼び出しの際も、メモリアドレスではなく、スタックが積まれていると考えれば、
メソッドの呼び出し時の動作も理解が可能です*2

逆に参照型の実体は、ヒープメモリに作成されます。
これは非常に大きなメモリの意味で、メモリアドレスによって管理されます。メモリアドレス経由で位置を特定し、アクセスするため、スタックに比べると速度が遅くなります。
.NET では、ヒープメモリ領域をカベージコレクタの管轄におく事で、メモリリークを回避します。
ガベージコレクタは不定期に、メモリ中の不要なインスタンスの削除(ガベージコレクション)や、メモリアドレスの再配置(コンパクション)を行う事から、
.NET 言語では基本的にメモリアドレスの直接操作(ポインタ操作)を許容しません。
このようなヒープ領域を、CLR(CommonLanguageRuntime:.NET のランタイム)では、「マネージヒープ」とも呼びます。

*1:ではなぜポインタと言わずに参照と呼ぶかですが、ガベージコレクタによって管理されていること、型を強制していること(別の型に無理矢理認識させる事が出来ない)等があります

*2:呼び出し規約は Wiki参照。http://ja.wikipedia.org/wiki/%E5%91%BC%E5%87%BA%E8%A6%8F%E7%B4%84