ASEホーム サイトマップ 交通アクセス お問い合わせ chineese english
             
                                          
最近の技術情報  一覧

                 
 


                 
 


                 
 


                 
 


                 
 


                 
 


                 
 


                 
 


                 
 


                 
 


                 
 


 
トップページ > 技術情報

.NET Framework を理解する(第1回)

2009年10月20日

.NET
 

.NET で開発を行うにあたり、クラスは避けて通れないものと思います。
しかし、クラスに慣れてしまうと、構造体を使う頻度が減ってしまいませんか?



値と参照を理解する

 

構造体も .NET では重要な意味があります。ここでは、忘れられがちな 構造体 ひいては、値と参照について復習したいと思います。


.NET の型は、CTS (CommonTypeSystem)で定義され、大きく二つの種類に分類されます。値型 と 参照型 です。


細かい派生は以下のような形になっています。


.NET の型の対応
値型 構造体 定義構造体(struct)
基本型 数値型 整数型 Byte, Sbyte, Char, Int16, UInt16, Int32, UInt32, Int64, UInt64
浮動小数点型 Single, Double
Decimarl
真偽値型 Boolean
列挙型(enum)
参照型 クラス class
オブジェクト object
デリゲート delegate
インターフェース interface


使い方、動き方の違い

 

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


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

    class Program
    {
        static void Main(string[] args)
        {
            // 構造体に null を挿入。
            // しかしコンパイルエラー。
            UserInfo azalea = null;
        }
    }
}

int , bool , double 等の基本型や 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# ではポインタとは呼ばず、参照と呼びます。

ポインタはあくまで値なので、演算次第で任意のメモリアドレスを参照できるのに対し、参照はガベージコレクタによって管理されていること、型を強制していること(別の型に無理矢理認識させる事が出来ない)、もともと存在するオブジェクトを指し示すことしか出来ない等があります。


値型を null にしたい場合

 

もちろん、DB との値の整合性などのために、整数や日付に null を許容したい場合もあります。この場合、Nullable ジェネリッククラスを利用します。


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

このときに、ひとつ注意が必要です。数値型は ToString の際に、フォーマットを指定できます。しかし、Nullable ジェネリッククラスは、引数つきの ToString をサポートしていません。


int value1 = 1000;
value1.ToString("N0");
// 1,000 の文字列を取得する。

int? value2 = 1000;
value2.ToString("N0"); // コンパイルエラー
// value2.value.ToString("N0"); // こちらが正解。

実装としてどうなっているのか

 

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


逆に参照型の実体は、ヒープメモリに作成されます。これは非常に大きなメモリの意味で、メモリアドレスによって管理されます。メモリアドレス経由で位置を特定し、アクセスするため、スタックに比べると速度が遅くなります。.NET では、ヒープメモリ領域をカベージコレクタの管轄におく事で、メモリリークを回避します。

ガベージコレクタは不定期に、メモリ中の不要なインスタンスの削除(ガベージコレクション)や、 メモリアドレスの再配置(コンパクション)を行う事から、.NET 言語では基本的にメモリアドレスの直接操作(ポインタ操作)を許容しません。このようなヒープ領域を、CLR(CommonLanguageRuntime:.NET のランタイム)では、「マネージヒープ」とも呼びます。



暗黙の型変換

 

C 言語の時代から脈々と受け継がれている機能です。例えば、float は double にキャスト抜きに代入出来ます。

変換ルールは、C# の場合小さい型から同型の大きな型への変換(拡大変換)のみをサポートします。しかし、VB.NET では、大きな型から小さな型への暗黙の変換(縮小変換)もサポートされます。


float fValue = 10.1f;
double dValue = fValue;

ボックス化とアンボックス化

全てのオブジェクトは、暗黙で System.Object 型を継承します。これは構造体でも例外ではなく、たとえば以下のコードはコンパイル、実行が可能です。


int intVal = 1000;
object value = intVal;

知っての通り、System.Object 型は参照型であり、上記のようなキャストを行うと、値型は暗黙で参照型に変換されます。この動作をボックス化と呼びます。また、逆の操作(参照型を値型に変換すること)をアンボックス化と呼びます。


ボックス化とアンボックス化は、変換コストがかかります。良く言われるものとしては、ToString を呼び出す際は、struct 内で ToString を override する事が推奨されます。これは、ToString メソッドをコールした際、基底クラス(Object)の ToString を呼ぶ為に、暗黙のボックス化が行われる為です。

また、 read/write の頻度が高い変数等は、struct を利用し、ボックス化を行わないよう配慮することで、処理の高速化が望めます。


次回は .NET Framework のコレクションクラスと利用方法。及び、ジェネリックとジェネリックコレクションの使い方について紹介致します。




関連するエントリー

 
このエントリーをはてなブックマークに追加
 
現在ページの上部へ戻る