Blog ブログ
あなたのソースコードはカプセル化できていますか?(C#)
皆様こんにちはエンジニアの島鼻と申します。
今回はふとC#で疑問に思った事です。
操作できてしまうCollection
上記のプロパティnumberListは、setはprivateですが、なんと中身が操作できてしまいます。
プロパティの privaate set は、プロパティへの代入を非公開にするものであってget したインスタンスのメソッド呼び出しを制御するものではないからです。これではカプセル化ができていません。C++のようなconst属性をつけたくなりますね。
ちょっと工夫しましょう。
IReadOnlyListを使う
System.Collections.Generic.List<T> は IReadOnlyList<T> を実装しています。
setterのメソッドは無いので、インデックスへの代入は出来なくなっています。
ところが、ここに落とし穴があります。
コレクションの変更 をガードしてはくれますが、コレクションの要素のメソッド呼び出しまでは禁止しないという状態です。そのため・・・。
操作できてしまっていますね。これは一向にreadonlyになってきません。
Generics の共変性を利用する
C# 4.0からの仕様で「generics の共変性」というのがあり、上記のような読み取りしか行われないような実装で暗黙の型変換が可能になります。
なぜ?可能にできるのかは以下です。
例えば上記のような変換を実際に行うには、問題があります。
以下のコードを見てみましょう。
この問題が起きる原因は「内容を書き換え可能」だからです。
しかし、書き換えられなければとくに問題は無いはずです。
例えば下記のような感じです。
上記は、変更できない IEnumerable で置き換えているのでobjs に myObjects を代入するのは問題無いです。
つまり、読み取り専用である事が保障出来れば、暗黙の型変換が許されると言う事です。
理屈は上記のような形ですが、明確にシンタックスが用意されています。
out パラメータは、型パラメータが共変であることを指定します。
型パラメータがインターフェイスのメソッドの戻り値の型としてのみ使用され、メソッド引数の型として使用されない事と、型パラメータがインターフェイスのメソッドのジェネリック制約として使用されないことが制約です。
参照:out (ジェネリック修飾子) (C# リファレンス)
これで、カプセル化できますね!
おまけ
ところで、UnityではC# 4.0のサポートはありますが、.NETが2.0なのでIReadOnlyList の定義では共変変換がありません。。。
なのでUnityでは、下記のように書くようにしました。
privateアクセスは、numberListPropertyFieldを使います。
AsReadOnlyは.NET Framework 2.0から追加されているのでUnityでも使えますね★