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 チェックがいりません。具体的にコードを示すと、
1 2 3 4 5 6 7 | NullObject *obj = [NullObject new ]; [obj callMethod]; // null を代入 obj = NULL; // 次の関数は呼び出されない [obj CallMethod]; |
ってな感じで、2回目の CallMethod は呼び出されません。 if ( obj != NULL ) というチェックがいらなくなってコードがシンプルになります。まあ、厳密性を重んじるならば NULL チェックをする「意図」は残しておいたほうがいいのですが、コードの安全性を考えるとこれで ok な気がします。
■拡張メソッドを使う
実は C# の拡張メソッドを使うと似たようなことができる、というのを先日知りました。元のクラスを NullObject にして、拡張メソッドを含むクラスを NullObjectExtention にしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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 なメソッドを呼び出せないので、代替案という訳です。
テスト用のプロジェクトとは別に、クラスライブラリを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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 で拡張メソッドを作る訳です。
テストコードはこんな感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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 の定番として、
1 2 | var data = ... var result = data.From( ... ).Where( ... ).Select( ... ).OrderBy( ... ); |
という書き方をした場合、From, Where, Select メソッド null を返すと不意に落ちてしまう訳です。ひとつの方法としては、それぞれのメソッドが null を返さないように実装すればよいのです。もうひとつの方法としては、先の拡張メソッドのテクニックを使って null で落ちないという方法が使えます。
まあ、変数を敢えて null で返したいときに使えるかなぁと。
ピンバック: .NET Clips