自分の備忘録として始めたブログですが、
幸い多くの方々に利用して頂くようになりました。
内容が古かったり見づらかったり遅かったりするので、
以下に引越しをすることにしました。
年内の完了を目指して少しづつ移行中です。
なんとか年度内には・・・移行しちゃいたい。

のんびり移行予定です。。。orz
よろしくお願いします。

https://dotnetcsharptips.com/

2015年04月10日

クラスと構造体のどちらを使うか

議論の前提

たとえば、身長と体重の値を持ち、BMIを算出するメソッドを持つクラスを定義するとする。
    public class Person
    {
        public double Height { get; set; }
        public double Weight { get; set; }

        public double GetBMI()
        {
            if (Height == 0 || Weight == 0) 
                throw new InvalidOperationException();

            return Weight / (Height * Height);
        }
    }
これをクラスではなく構造体として定義すると以下のようになる。
    public struct Person
    {
        public double Height { get; set; }
        public double Weight { get; set; }

        public double GetBMI()
        {
            if (Height == 0 || Weight == 0) 
                throw new InvalidOperationException();

            return Weight / (Height * Height);
        }
    }

違いはclassstructに変更しただけ。
C#ではクラスも構造体も状態(フィールド)と振舞い(メソッド)とを持てるので、どちらでも実装可能な場合が多い。そのため、どちらで実装すべきかが議論となる場合がある。

構造体の特徴

値型
・new演算子を使わなくてもインスタンス化される。
・System.ValueTypeを直接継承し、自身は継承元にもなれない(但し、インタフェースを実現はできる)
・デフォルトコンストラクタ(引数なしのコンストラクタ)を定義できない。
・フィールド初期化子による初期化は定義できない。
※コンパイルされたアセンブリをみるとデフォルトコンストラクタはコンパイラにより暗黙的に生成されDefault Zeroingという特殊な(効率的な)初期化処理が行われる。
 フィールド初期化はコンパイラによりコンストラクタに移動させられるのだが、移動先は別用途で使用されているため定義できない。

効率(パフォーマンス)の問題

MSDNのプログラミングガイドから引用します。

構造体は軽量オブジェクトを表すのに適しています。参照型の機能が必要ないのなら、小さいクラスとして実装するよりは構造体として定義した方が、システムにより効率的に処理されることが期待できます。

実際に計測してみると(かなりハイスペックなマシンでデバッグビルド版で計測)、上のサンプルコードにあるクラスと構造体とを10万個づつインスタンス化してもいづれも2msと差はなし。100万回でクラスが約100msに対し構造体が30msと約3倍の効率の良さは伺える。
これらを「参照型の機能」として、つまりBoxingされるようObject型にキャストする処理を介在させた場合、クラスも構造体も120msぐらいであった。

これって、実際の使用条件等を考えるとほとんど構造体を使う理由がないように思えます。

参考 System.IO名前空間

.NET Framework4.5 のクラスライブラリでSystem.IO名前空間に定義されている型を数えると、クラスが39に対し、構造体はWaitForChangedResultの1つのみ。そのメンバは以下のとおり。
public struct WaitForChangedResult
{
    public WatcherChangeTypes ChangeType { get; set; }
    public string Name { get; set; }
    public string OldName { get; set; }
    public bool TimedOut { get; set; }   
}

つまり、このぐらいのメンバ数がクラスでなくて構造体として定義すべきかを考える目安ではないかと思われる。
一般的には構造体のサイズは16バイト以下だと効率が良いとされる。それ以上だと値をコピーするよりポインタ(参照)をコピーする方がコストが低い場合が多いらしい。但し場合によりけりだそうです。


構造体使用時の注意点

変数に代入する場合、クラスだと参照(つまりポインタ)がコピーされるのに対し、構造体(を含む値型)では、値そのものがコピーされる。
        var person1 = new Person { Height = 1, Weight = 75 };
        var person2 = person1;
        person1.Height = 2;
        // ここで、Personがクラスとして定義されていれば2、構造体なら1のままとなる
        var heightC = person2.Height; 

構造体を使う必要があるケース


以上より「物凄く大量にインスタンス化され」「メンバはあまり多くなく」「参照型にキャストされることはなく」かつ「ミリ秒単位での効率化を勘案する必要がある」という極く限られた場合を除き、構造体を使う場所は無いのだが、そういう縛りがなくても構造体を使う必要な箇所はいくつかある。

たとえば、Win32APIなどのようにネイティブのDLLを.NET側から呼び出す場合、引数として構造体を渡す必要がある場合がある。
マトメ:多分、使いどころはそのぐらい。
posted by RR at 16:55 | Comment(0) | Tips | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

※ブログオーナーが承認したコメントのみ表示されます。