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