[Effective C#] 項目23 クラス内のオブジェクトの参照を返さないようにすること
本日の Effective C# は「項目23 クラス内のオブジェクトの参照を返さないようにすること」です。
これ大事なんですけどね。守りたいんですが、ついつい面倒で…。
public class Foo { public int Value { get; set; } } public class Sample { private Foo _sampleFoo = new Foo(); public Foo SampleFoo { get { return _sampleFoo; } } }
こんなコードがあったときに、readonly で SampleFoo として公開しているので、_sampleFoo は変更されたくないんですが。
var sample = new Sample(); // これはコンパイルエラー。想定通り。 sample.SampleFoo = new Foo(); // これは動作するが、意図しない動作。 sample.SampleFoo.Value = 1; // これも動作するが、意図しない動作。 var foo = sample.SampleFoo; foo.Value = 2;
このように、インスタンスは変更できないが、インスタンスの値そのものが変更できてしまいます。これを防ぐためには、
public interface IReadOnlyFoo { int Value { get; } } public class Foo : IReadOnlyFoo { public int Value { get; set; } } public class Sample { private Foo _sampleFoo = new Foo(); public IReadOnlyFoo SampleFoo { get { return _sampleFoo; } } }
などとします。まあ、余計に interface を書かなければいけないので、結構面倒なんですよね。まあ、ReSharper の助けを借りればすぐなのですが。
ちなみに、値型の場合はこのような心配はありません。
public class Foo { public Point Point { get; set; } }
と書いてあるとき、
var foo = new Foo(); // コンパイルエラー。 foo.Point.X = 1; // 動作はするが、コピーを変更しているため、foo.Point の値は変化しない。 var point = foo.Point; point.X = 2;
となるので、readonly プロパティであれば、値を変更することは不可能です。
実は前者がコンパイルエラーになるのをはじめて知りました。
さて、書籍の方に戻って。DataSet (というか DataTable) を readonly で公開する方法が書かれています。
public class MyBusinessObject { // private データメンバにアクセスする読み取り専用プロパティ private DataSet _ds; public IList this[string tableName] { get { DataView view = _ds.DefaultViewManager.CreateDataView(_ds.Tables[tableName]; view.AllowNew = false; view.AllowDelete = false; view.AllowEdit = false; return new; } } }
このように、IList を用いて公開しています。また、行の追加・削除・編集を禁止しているところも興味深いです。(この書籍は C# 1.1 向けなのであれですが、実際のコードではジェネリックを使うべきだと思います)
また、追加・削除・編集は禁止しなくても、DataTable にはこれらのイベントがあるので、変更の通知を受け取ることができます。なので、知らないうちに書き換えられたということを避けられます。まあ、かなり頑張らないといけないですけど…。
コメントを追加