拡張メソッドの活用
自分の理解を確認するために拡張メソッドの活用について整理したいと思います。
拡張メソッドの活用例として特定のクラスを通じて取得するSingletonというものを考えてみました。
通常Singletonは利用する側が任意の個所で取得するようになっていると思います。
そこで拡張メソッドを利用して、定義する側でSingleton取得するクラス限定するというものです。
以下の例ではSingletonSomeAccessインターフェイスを実装するクラス、つまりHasSomeAccessクラスのオブジェクトを経由してISomeインターフェイスをもつSingletonを取得できる様にしています。
public interface ISome { void SaySome(); } class SomeSingletonClass : ISome { #region ISome メンバ public void SaySome() { Trace.WriteLine("I am single."); } #endregion } public interface SingletonSomeAccess { } public static class SingletonAccessExtention { private static ISome singleton = null; private static void CreateSingleton() { singleton = new SomeSingletonClass(); } public static ISome GetSome(this SingletonSomeAccess obj) { if (singleton == null) CreateSingleton(); return singleton; } } public class HasSomeAccess : SingletonSomeAccess { private ISome some { get { // 中からアクセス return this.GetSome(); } } public void SomeSays() { some.SaySome(); } } class Program { static void Main(string[] args) { HasSomeAccess obj = new HasSomeAccess(); obj.SomeSays(); ISome some = obj.GetSome(); some.SaySome(); // 外からアクセス } }
念のために補足しますがこれはSingletonパターンではなく、上記のコードを同じアセンブリに配置すれば直接SomeSingletonClassクラスのオブジェクトを生成することができます。
それを避けたいのであれば直接生成しない「お約束」にするか別のアセンブリに配置するなどを行う必要があります。
SingletonSomeAccessを実装したクラスのオブジェクトを通じてSingletonを取得できるというのがミソでクラスの中からだけでなく外からも取得できます。
さて、ここまで色々と検証してきて拡張メソッドのポイントは
- 機能を付加する対象範囲を特定する
- 機能を後付けで付加して利用するスコープを任意にできる
- インターフェイスやクラスに対してあくまでも外側から機能を付加する
だと理解しました。
「1. 機能を付加する対象範囲を特定する」という点について拡張メソッドで追加する範囲の指定の仕方としては以下の分類があると考えています。
- あらゆるクラス
- 特定のクラスおよびその派生クラス
- 特定のインターフェイスを実現するグループ
例えばクラスにトレースの機能を追加する場合に「あらゆるクラス」に追加する場合は以下の様にobjectクラスに対する拡張とします。
public static class TraceExtention { public static void PutTrace(this object obj) { Trace.WriteLine(obj); } public static void PutTrace(this object obj, string message) { Trace.WriteLine(obj.ToString() + " : " + message); } }
あるいは以下のようにジェネリックな型に対する拡張としても良いかもしれません。
public static class TraceExtention { public static void PutTrace<T>(this T obj) { Trace.WriteLine(obj); } public static void PutTrace<T>(this T obj, string message) { Trace.WriteLine(obj.ToString() + " : " + message); } }
「特定のクラスおよびその派生クラス」に追加する場合、例えばSomeClassクラスおよびその派生クラスへの拡張は以下の様にします。
class SomeClass { } class SomeSubClass : SomeClass { } public static class TraceExtention { public static void PutTrace(this SomeClass obj) { Trace.WriteLine(obj); } public static void PutTrace(this SomeClass obj, string message) { Trace.WriteLine(obj.ToString() + " : " + message); } }
「特定のインターフェイスを実現するグループ」に追加するということはアスペクトやコンテキストで表現されるようなデータやドメイン構造と違う次元で表現されるグループに対して共通的に機能を拡張するということで「機能を後付けで付加して利用するスコープを任意にできる」にも通じることだと思います。
例えばTraceSupportインターフェイスを実現するクラスおよびその派生クラスへの拡張は以下の様にします。
interface TraceSupport { } class SomeClass { } class SomeSubClass : SomeClass, TraceSupport { } public static class TraceExtention { public static void PutTrace(this TraceSupport obj) { Trace.WriteLine(obj); } public static void PutTrace(this TraceSupport obj, string message) { Trace.WriteLine(obj.ToString() + " : " + message); } }
「2. 機能を後付けで付加して利用するスコープを任意にできる」というのは「インターフェイスやクラスに対してあくまでも外側から機能を付加する」ということの単なる特性なのですが、例えば既存のintなどの型に対してそのコンテキストに合わせて機能を付加できるということはデータあるいはドメインの表現としての関連の構造とそれを利用するコンテキストに依存する機能の分離というJim CoplienのDCIアーキテクチャに通じるものがあると思います。
利用するスコープを任意にするという意味は拡張メソッドが存在するアセンブリとusingなどで名前空間を参照した範囲だけに影響するということです。
「3. インターフェイスやクラスに対してあくまでも外側から機能を付加する」というのは、拡張メソッドの実現手段を見れば明らかですが、従来からのstaticメソッドによるライブラリ的な機能の提供と同じことです。
そう考えるとつまらないように思えますが、拡張メソッドとして既存のクラスにメソッドが存在するかの様に見えるソースコード上での表現は、データとアルゴリズムが分離されている手続き的なライブラリ的な発想から宣言的に対象物の属性として機能が提供されるオブジェクト指向的な発想へと転換してくれます。
ソースコードやコーディングの作業が思考と知識の表現であったとすればこの発想の違いの影響は思った以上に重要だと思います。
私の中でのC#の拡張メソッドの理解を整理すると上記の様になります。
このまとめが他の人にとって有益な情報となるかどうかはわかりませんし、そもそも間違った理解が含まれているかもしれないのですが、建設的な議論の題材にでもなれば幸いだと思います。