C#のデリゲートがよくわからんという同僚さんのために、なんかいいサンプルコードを書きたいと思ったんですが、なかなか難しいですねえ。
分かりやすくて、なおかつ実用的なサンプルが思いつかないです。
おいらの中では「デリゲート = メソッド(処理)をあたかも変数のようにして使いまわせるモノ」みたいな感じの理解です。(かなり適当・・・)
たとえば、スクリプト系の言語だと何でも実行時に解決しちゃうんで、デリゲートなんてものが必要ありません。
JavaScript(というかJScript)で書くとこんな感じです。
main(); function main() { var methodContainer = []; methodContainer.push(sayHelloInEnglish); methodContainer.push(sayHelloInJapanese); for (var i in methodContainer) { var helloMethod = methodContainer[i]; var name = "John"; var message = helloMethod(name); WScript.Echo(message); } } function sayHelloInEnglish(name) { return "Hello, " + name; } function sayHelloInJapanese(name) { return "こんにちは、" + name; }
実行結果: Hello, John こんにちは、John
JavaScriptは関数 = オブジェクト(変数)なので、こんな書き方ができちゃいます。
// sayHelloInEnglishは関数
methodContainer.push(sayHelloInEnglish);
「メソッドを変数みたいに扱う」という感覚は、こういうスクリプト系のコードの方が分かりやすいかな〜と個人的には思います。
で、C# 2.0(古い。。。)で書くとこんな感じ。
using System; using System.Collections.Generic; public static class Program { private delegate string SayHelloDelegate(string name); public static void Main(string[] args) { IList<SayHelloDelegate> methodContainer = new List<SayHelloDelegate>(); methodContainer.Add(new SayHelloDelegate(SayHelloInEnglish)); methodContainer.Add(new SayHelloDelegate(SayHelloInJapanese)); foreach (SayHelloDelegate helloMethod in methodContainer) { string name = "John"; string message = helloMethod(name); Console.WriteLine(message); } } private static string SayHelloInEnglish(string name) { return "Hello, " + name; } private static string SayHelloInJapanese(string name) { return "こんにちは、" + name; } }
C#だとコンパイラが色々と事前にチェックしなきゃいけないので、何でもかんでも変数にはできません。
事前に変数化したいメソッドの引数と戻り値(いわゆるシグニチャ)をハッキリさせておかなきゃならない。
なのでJavaScriptでは出てこなかったデリゲートが必要になってきます。
このサンプルコードでは戻り値がstring、引数が1個でstringであることを「SayHelloDelegate」が規定しています。
そして「SayHelloInEnglish」と「SayHelloInJapanese」はそのシグニチャに従っています。
delegate string SayHelloDelegate(string name) string SayHelloInEnglish(string name) string SayHelloInJapanese(string name)
さらに、デリゲートが間に入ることでシグニチャが確定するため、コンパイラは以下のコードが実行可能であることを保証できます。
// helloMethodは変数化されたメソッド // ここでは「SayHelloInEnglish」または「SayHelloInJapanese」のいずれか string message = helloMethod(name);
ちなみに、無名delegateという機能を使うと「SayHelloInEnglish」や「SayHelloInJapanese」の定義が不要になります。
using System; using System.Collections.Generic; public static class Program { private delegate string SayHelloDelegate(string name); public static void Main(string[] args) { IList<SayHelloDelegate> methodContainer = new List<SayHelloDelegate>(); methodContainer.Add(delegate(string name) { return "Hello, " + name; }); methodContainer.Add(delegate(string name) { return "こんにちは、" + name; }); foreach (SayHelloDelegate helloMethod in methodContainer) { string name = "John"; string message = helloMethod(name); Console.WriteLine(message); } } }
少々トリッキーなコードになりますが、その処理を再利用する必要がなかったり、メソッド内のローカル変数を手軽に使い回したいときなんかには結構便利だったりします。
おいらがデリゲートを定義したり、使ったりする時のイメージはこんな感じです。
でもこんな説明でデリゲートへの理解が少しでも深まりますかねえ?
自分でもあまり自信がありませんが、何かの参考になれば幸いです。
補足: コマンドパターンで書き換えた場合
ちなみに、このロジックをあえてコマンドパターンで書き換えるとこんな感じになります。
using System; using System.Collections.Generic; public static class Program { public static void Main(string[] args) { IList<SayHelloCommand> methodContainer = new List<SayHelloCommand>(); methodContainer.Add(new SayHelloInEnglishCommand()); methodContainer.Add(new SayHelloInJapaneseCommand()); foreach (SayHelloCommand helloMethod in methodContainer) { string name = "John"; string message = helloMethod.SayHello(name); Console.WriteLine(message); } } private abstract class SayHelloCommand { public abstract string SayHello(string name); } private class SayHelloInEnglishCommand : SayHelloCommand { public override string SayHello(string name) { return "Hello, " + name; } } private class SayHelloInJapaneseCommand : SayHelloCommand { public override string SayHello(string name) { return "こんにちは、" + name; } } }
この場合、抽象クラスではなくインターフェースを使って書き換えることもできます。(だから何ということはないですが。。。)
using System; using System.Collections.Generic; public static class Program { public static void Main(string[] args) { IList<ISayHelloCommand> methodContainer = new List<ISayHelloCommand>(); methodContainer.Add(new SayHelloInEnglishCommand()); methodContainer.Add(new SayHelloInJapaneseCommand()); foreach (ISayHelloCommand helloMethod in methodContainer) { string name = "John"; string message = helloMethod.SayHello(name); Console.WriteLine(message); } } private interface ISayHelloCommand { string SayHello(string name); } private class SayHelloInEnglishCommand : ISayHelloCommand { public string SayHello(string name) { return "Hello, " + name; } } private class SayHelloInJapaneseCommand : ISayHelloCommand { public string SayHello(string name) { return "こんにちは、" + name; } } }
「デリゲートは分からないけど、コマンドパターンなら分かる!」という人にはもしかしたら参考になるかもです。
補足2: JavaScriptのコードをアレンジしてみる
前述した通り、JavaScriptでは「関数 = オブジェクト(変数)」です。
実はこんなコードを書いても動いちゃいます。
var sayHelloInEnglish = function(name) { return "Hello, " + name; }; var sayHelloInJapanese = function(name) { return "こんにちは、" + name; }; var main = function() { var methodContainer = []; methodContainer.push(sayHelloInEnglish); methodContainer.push(sayHelloInJapanese); for (var i in methodContainer) { var helloMethod = methodContainer[i]; var name = "John"; var message = helloMethod(name); WScript.Echo(message); } }; main();
注目すべきは以下の部分です。
このコードだと関数が変数として扱われているということが明示的につかめるんじゃないでしょうか。
var sayHelloInEnglish = function(name) var sayHelloInJapanese = function(name)
また、C#の無名delegateと同じようなコードを書くこともできます。
var main = function() { var methodContainer = []; methodContainer.push(function(name) { return "Hello, " + name; }); methodContainer.push(function(name) { return "こんにちは、" + name; }); for (var i in methodContainer) { var helloMethod = methodContainer[i]; var name = "John"; var message = helloMethod(name); WScript.Echo(message); } }; main();
JavaScriptはJavaScriptで、興味深い仕様がたくさんあります。
詳しく知りたい方は以下の書籍がオススメです。
- 作者: David Flanagan,村上列
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/08/14
- メディア: 大型本
- 購入: 52人 クリック: 1,011回
- この商品を含むブログ (271件) を見る
JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
- 作者: Douglas Crockford,水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/12/22
- メディア: 大型本
- 購入: 94人 クリック: 1,643回
- この商品を含むブログ (190件) を見る
補足3: マルチキャストデリゲートを使ってみる
マルチキャストデリゲートという機能を使うと、こんな感じに書き換えられます。
using System; using System.Collections.Generic; public static class Program { private delegate void SayHelloDelegate(string name); public static void Main(string[] args) { SayHelloDelegate methodContainer = null; methodContainer += new SayHelloDelegate(SayHelloInEnglish); methodContainer += new SayHelloDelegate(SayHelloInJapanese); string name = "John"; methodContainer(name); } private static void SayHelloInEnglish(string name) { Console.WriteLine("Hello, " + name); } private static void SayHelloInJapanese(string name) { Console.WriteLine("こんにちは、" + name); } }
さらに無名delegateを使うとこんな風になります。
using System; using System.Collections.Generic; public static class Program { private delegate void SayHelloDelegate(string name); public static void Main(string[] args) { SayHelloDelegate methodContainer = null; methodContainer += delegate(string name) { Console.WriteLine("Hello, " + name); }; methodContainer += delegate(string name) { Console.WriteLine("こんにちは、" + name); }; methodContainer("John"); } }
さらにC# 3.0では・・・と続くはずなのですが、手軽に確認できる環境がないため、ここで切り上げます。 m(_ _)m
=> 補足5にて書き換えてみました。
補足4: なんとなくRubyで書いてみる
Rubyは全くの初心者ですが、見よう見まねで書いてみました。
単なるRubyへの憧れです、はい。
def say_hello_in_english(name) "Hello, " + name end def say_hello_in_japanese(name) "こんにちは、" + name end method_container = [] method_container.push method :say_hello_in_english method_container.push method :say_hello_in_japanese method_container.each do |hello_method| name = "John" message = hello_method.call name puts message end
無名delegateっぽく書くとこんな感じ?
method_container = [] method_container.push lambda{|name| "hello, " + name} method_container.push lambda{|name| "こんにちは、" + name} method_container.each do |hello_method| name = "John" message = hello_method.call name puts message end
補足5: C#3.0で書き換えてみる
今度はC# 3.0で書き換えてみました。
ラムダ式ってのを使うとdelegateの宣言がいらなくなくなるみたいですね〜。
ループ処理もRubyみたいな書き方ができるので、ソースがさらに簡潔になります。
会社の環境もさっさと最新の開発環境に移行してもらいたいのですが。う〜ん。。。
using System; using System.Collections.Generic; public static class Program { public static void Main(string[] args) { var methodContainer = new List<Func<string, string>>(); methodContainer.Add((name) => "Hello, " + name); methodContainer.Add((name) => "こんにちは、" + name); methodContainer.ForEach((helloMethod) => { var name = "John"; var message = helloMethod(name); Console.WriteLine(message); }); } }
元のロジックを意識しつつ、とことん短く書いてやろうと思うとこんな感じ?
かなりトリッキーで一体何をやりたいのかわからないかもしれませんが。。。 (^ ^;;
using System; using System.Collections.Generic; public static class Program { public static void Main(string[] args) { new List<Func<string, string>> { ((name) => "Hello, " + name), ((name) => "こんにちは、" + name) } .ForEach((helloMethod) => { var name = "John"; var message = helloMethod(name); Console.WriteLine(message); }); } }
補足6: F#で書き換えてみる
調子に乗ってF#版まで作ってみました。
全くもって関数型脳は習得できていないので、たぶん全然関数型言語らしくないんじゃないかと思います・・・。
あまり参考になさらぬよう。(- -;;
let printMessage helloFunc = let name = "John" let message = helloFunc name printfn "%s" message List.iter printMessage [(fun name -> "Hello, " + name); (fun name -> "こんにちは、" + name)]
可読性を無視したらこうなりました。ただの変態コードですね。。。
List.iter (fun helloFunc -> printfn "%s" (helloFunc "John")) [(fun name -> "Hello, " + name); (fun name -> "こんにちは、" + name)]