LoginSignup
4
2

More than 5 years have passed since last update.

readonlyな構造体で注意すべきコト

Last updated at Posted at 2016-11-24

どーいうことだってばよ

例えばこんな構造体をこさえたとする

public class Counter
{
    public int Count { get; private set; }

    public void Incliment() => ++Count;

}


public struct SampleStruct
{
    public SampleStruct(int value):this()
    {
        Value = value;
        Counter=new Counter();
    }
    public int Value { get; set; }

    public int Count { get; private set; }

    public Counter Counter { get; }

    public void InclimentCount()
    {
        Counter.Incliment();
        ++Count;
    }
}

でこいつが、何かしらのreadonlyなメンバフィールドになったとき、予想外の結果になるので注意しましょうってお話なので、一つおつきあい頂ければこれ幸い。

とりあえずこさえてみる

とりあえずこんな風にメンバフィールドとしてこさえてみる。


public class Envelope
{
    private readonly SampleStruct _immutable = new SampleStruct(42);
    private SampleStruct _mutable = new SampleStruct(42);
...

で、下記のようにメソッドを追加してみる。


public class Envelope
{
    private readonly SampleStruct _immutable = new SampleStruct { Value = 42};
    private SampleStruct _mutable = new SampleStruct {Value = 114514};

    public void SetValue(int value)
    {
        //CS1648が発生。
        _immutable.Value = value;
        _mutable.Value = value;
    }
}

この場合、コメントにあるとおり、readonlyなメンバに対するプロパティの設定操作は許容されずコンパイルエラーになる。
そりゃそーだ、なにせ読み取り専用なのだから。

内部状態を変更するメソッドはどーなるのか

内部状態を変更するメソッドを実行した場合、とても厄介なことになる。
とりあえず、問題起こすサンプルは以下の通り。

public class Envelope
{
    private readonly SampleStruct _immutable = new SampleStruct { Value = 42};
    private SampleStruct _mutable = new SampleStruct {Value = 114514};

    public void InclimentCount()
    {
        _mutable.InclimentCount();
        _immutable.InclimentCount();


        //1
        Console.WriteLine($"_mutable.Count:{_mutable.Count}");
        //1
        Console.WriteLine($"_mutable.Counter:{_mutable.Counter.Count}");

        Console.WriteLine();
        //0のまま!
        Console.WriteLine($"_immutable.Count:{_immutable.Count}");

        //1
        Console.WriteLine($"_immutable.Counter:{_mutable.Counter.Count}");
    }

}

さてこの場合、結果は、値型のメンバフィールドの値型はreadonly制約が効くので変更されない。
しかも、メソッド呼び出しそのものはコンパイルエラーもランタイムエラーも発生させず、結果が予想外になるというとんでもなく厄介な結果となる。
蛇足ながら、なんでエラーにならず、結果が予想外になるかはコンパイル時に以下のような結果を生成するから

public void DecompileIncliment()
{
    //_immutable.InclimentCount();
    SampleStruct anonymous = _immutable;
    anonymous.InclimentCount();


    //0
    Console.WriteLine($"_immutable.Count:{_immutable.Count}");
    //1
    Console.WriteLine($"_immutable.Counter:{_immutable.Counter.Count}");

}

また、readonlyな値型の参照型メンバフィールドはしれっと変更できちゃうのでこれも又注意しないとマズい。

まとめ

ということで、readonlyな値型に対する間接的なmutableな操作は時として相当厄介なバグを生み出すので、注意すべきかと。
おつきあい頂有り難うございました。

4
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2