WCFサービスを調べているときに見つけたので、ちょっとメモ的に。下記の SafeInvoke メソッドのところです。
Method call if not null in C# – Stack Overflow
http://stackoverflow.com/questions/872323/method-call-if-not-null-in-c-sharp
objective-c には便利な機能があって、変数が null の場合はメソッドを呼び出さないのです。このために null チェックがいりません。具体的にコードを示すと、
NullObject *obj = [NullObject new]; [obj callMethod]; // null を代入 obj = NULL; // 次の関数は呼び出されない [obj CallMethod];
ってな感じで、2回目の CallMethod は呼び出されません。 if ( obj != NULL ) というチェックがいらなくなってコードがシンプルになります。まあ、厳密性を重んじるならば NULL チェックをする「意図」は残しておいたほうがいいのですが、コードの安全性を考えるとこれで ok な気がします。
■拡張メソッドを使う
実は C# の拡張メソッドを使うと似たようなことができる、というのを先日知りました。元のクラスを NullObject にして、拡張メソッドを含むクラスを NullObjectExtention にしておきます。
namespace SampleNullObjectExtention { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { var obj = new NullObject(); Debug.Print("goMessage pre"); obj.goMessage("masuda"); Debug.Print("goMessage after"); obj = null; Debug.Print("goMessageEx pre"); obj.goMessageEx("tomoaki"); Debug.Print("goMessageEx pre"); } } public static class NullObjectExtention { public static void goMessageEx( this NullObject obj, string msg) { if (obj != null) { obj.goMessage(msg); } } } public class NullObject { public void goMessage(string msg) { Debug.Print("in goMessage {0}", msg); } } }
すると、button1 をクリックしたときに、obj を null に設定しておいても、goMessageEx メソッド呼び出しは大丈夫なんですね。なるほど。拡張メソッド側で this で参照させて null チェックをするという技です。LINQ の内部でも使っているのかもしれません。
なかなか面倒ですが、メソッドを全てラップしてしまって、ラップした方のメソッドを使うというルールにすれば ok なんですが、いやいや、そうはいきません。インテリセンスで「goMessage」が出てくるならば、それを使ってしまうかもしれない。ならば、obj が null の場合は通常通り落ちてしまう訳です。使えない技ですね~という感じになってしまいます。
■別のアセンブリに隠す(internal protected)
C++のようにfriendで制限ができればよいのですが、.NETにはありません。その代り「internal protected」というちょっと中途半端な(便利な)範囲設定ができます。
拡張メソッドは、protected なメソッドを呼び出せないので、代替案という訳です。
テスト用のプロジェクトとは別に、クラスライブラリを作成します。
namespace CheckNullObjectLib { /// /// NullObject の拡張クラス /// public static class NullObjectExtention { /// /// SetMessage の Wrapper /// /// <param name="me" /> /// <param name="msg" /> public static void SetMessage(this NullObject me, string msg) { if (me != null) { me._setMessage(msg); } } /// /// GetMessage の Wrapper /// /// <param name="me" /> /// <param name="msg" /> public static string GetMessage(this NullObject me) { if (me != null) { return me._getMessage(); } // あえて空文字列を返す return ""; } } /// /// 本体の NullObject クラス /// public class NullObject { protected internal string _msg = ""; /// /// アクセス制限を protected internal(同じアセンブリのみ)にしておく /// /// <param name="msg" /> protected internal void _setMessage(string msg) { _msg = msg; } protected internal string _getMessage() { return _msg; } } }
本体の NullObject クラスで公開するメソッド/プロパティは、全て protected internal にしておきます。こうすると、同じアセンブリ内のクラスからのみアクセスが可能になります。これを、ラップする NullObjectExtention クラスでは、public で拡張メソッドを作る訳です。
テストコードはこんな感じになります。
using CheckNullObjectLib; namespace CheckTestNullObject { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private NullObject obj; /// /// set button /// /// <param name="sender" /> /// <param name="e" /> private void button1_Click(object sender, EventArgs e) { obj = new NullObject(); obj.SetMessage(textBox1.Text); } /// /// get button /// /// <param name="sender" /> /// <param name="e" /> private void button2_Click(object sender, EventArgs e) { // いきなり set button を押して, // obj が null の状態でも大丈夫 textBox2.Text = obj.GetMessage(); } } }
内部で保持している NullObject オブジェクト obj は、set button を押したときに new する訳ですが、間違って get button を押したとしても GetMessage メソッドで例外が発生しません。なんと便利なッ!!! objective-c の技がッ!!! って思いますが、まぁ、コーディングの基礎しては「きちんと obj は初期化しておこうね」とか「get button を押した時に null チェックをしようね」というのが C# のコーディングとして正しいやり方ですね。
■どんなところで使うのか?
使い処としては、null チェックを省くってのもそうなのですが、メソッドチェーンのところで途中に null を含められるというメリットがあります。
LINQ の定番として、
var data = ... var result = data.From( ... ).Where( ... ).Select( ... ).OrderBy( ... );
という書き方をした場合、From, Where, Select メソッド null を返すと不意に落ちてしまう訳です。ひとつの方法としては、それぞれのメソッドが null を返さないように実装すればよいのです。もうひとつの方法としては、先の拡張メソッドのテクニックを使って null で落ちないという方法が使えます。
まあ、変数を敢えて null で返したいときに使えるかなぁと。
ピンバック: .NET Clips