atsukanrockのブログ

Microsoft系技術を中心にぼちぼち更新します

構造体ではデフォルトコンストラクタを定義できない

はじめに

C#では、構造体のデフォルトコンストラクタを定義できない。このことを不思議に思っている人は少なくないのではないか。私もその一人だ。本エントリでは、そのジレンマを嘆く。

なぜ構造体のデフォルトコンストラクタを定義できないのか

人力検索にも質問があった。この人力検索では、パフォーマンスを上げることが目的でそのような仕様としているようだ、という結論になっている。それができないならできないで仕方がないのだが、できないせいでなかなか構造体を使う機会がない。

私が構造体を使いたい理由

私が構造体を使いたい理由は、理由の重要度順に以下のとおりである。

  1. 構造体の変数がnullとならない
  2. パフォーマンス

つまり私にとっては、パフォーマンス面での利点より、変数がnullとならないことの方が重要なのだ。例えば、以下のようなクラスがあるとする。

public class Hoge
{
}

すると、Hogeクラスを引数とするメソッド内では、引数がnullかどうかをチェックをして、もしnullであればArgumentNullExceptionをthrowするというコードを記述するのがお決まりだ*1Javaだとこのチェックは行わずにNullPointerExceptionが発生するのに任せるのが普通だ。しかし.NETだと、JavaのようにNullReferenceExceptionが発生するのに任せるのではなく、以下のようなコードを記述するのが普通なのである。

/// <summary>fooする。</summary>
/// <returns>ほげ。</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="hoge" /><c>null</c> が指定された。
/// </exception>
public void foo(Hoge hoge)
{
    if (hoge == null)
    {
        throw new ArgumentNullException("hoge");
    }

    // メソッドの処理...
}

これはかなりの苦痛を伴う。nullチェックのコードを記述するのに加え、ドキュメントコメントを記述することになる。私はこのお決まりの作業を、クリップボードビューア(Charu3)を使ってできるだけ省力化しているが、それでもかなり苦痛である。
そこで、軽量な型ならできるだけ構造体にしたい。なぜなら、もし上記のHogeクラスが構造体なら、nullチェックをしなくてよくなるからだ。nullチェックをするのが苦痛ならやらなければ良いではないかという意見もあろうが、郷に入っては郷に従うのが私のやり方なので*2、nullチェックはやらざるを得ない。

構造体を使いづらい理由

しかし、構造体にするとやっかいなのが、冒頭で記述した、デフォルトコンストラクタを定義することができないという仕様だ。例えば、以下のような構造体を考える。

public struct Fuga
{
    public Fuga(String sValue)
    {
        if (sValue == null)
        {
            throw new ArgumentNullException("sValue");
        }
    }

    private readonly String _sValue;

    /// <summary>
    /// このオブジェクトの値を取得する。
    /// <c>null</c> でないことが保証される…?
    /// </summary>
    public String Value
    {
        get { return _sValue; }
    }
}

Fuga構造体は、唯一のフィールドである_sValueがreadonlyであり、コンストラクタでnullでないことを確かめているため、Valueプロパティがnullである可能性はない…かに見える。しかし、ここで問題となるのが構造体のデフォルトコンストラクタだ。
Fuga構造体は構造体なので、暗黙的にデフォルトコンストラクタが定義されており、そのコンストラクタでは_sValueフィールドをnullで初期化する。つまり、構造体のフィールドにクラスがある場合、そのフィールドがnullになる可能性を排除することができないのだ。結果、Fuga構造体は、構造体を利用する側にnullチェックを課する仕様となってしまう。
この仕様は良くない。Fuga構造体のインスタンスがせっかくimmutable(不変)であり、Valueプロパティ値としてnullが不正であることが分かっているにも関わらず、Valueプロパティがnullである可能性があるのだ。Fuga構造体をクラスにすれば、この問題は解決する*3
immutableな型には、スレッドセーフであるという利点もあるが、その型のインスタンスプロパティの値チェックを、コンストラクタで一度だけ行えば良いという点も大きな利点だ。その恩恵を受けるためにも、私はimmutableな型を実装する際には、たいてい構造体よりクラスを使う。

結論

immutableな型を実装する際、クラスとすべきなのか構造体とすべきなのか。私なりの結論は以下のとおりだ。

インスタンスフィールドにクラスのインスタンスを持つかどうか クラス/構造体
持つ クラス
持たない 構造体

クラスを使用した場合、そのインスタンスを引数として受け取るメソッドのコードでは、前述のようにnullチェックをしなければならなくなる。しかし、そのプロパティを取得するたびにnullチェックをすることに比べれば、その苦痛(およびパフォーマンス上のマイナス)は幾分ましであろう。構造体にデフォルトコンストラクタさえ定義できれば、全て解決するのに…。

*1:メソッドがprivateでない場合

*2:とは言っても、ネーミング規約が.NET的ではないのだが…それはいちサラリーマンの悲哀である

*3:ドキュメントコメントにある、「…?」も「。」に変更できる