【C#】メソッド呼び出し側が、処理自体を渡す方法(メソッドが引数、無名メソッド)【実践的】【必要最低限】


Lispや JavaScriptで簡単につかえる引数部分に関数を渡すやり方と同じです。
for文の一部の処理だけ変更したい時、2つの大きなメソッドを作りたくもありません。
そういうときに難しいですが少し使えます。手間もLispに比べて下準備が必要なのがネックです。

      .... 

     ___XXX();

      .... 

     
       ___XXX() {
          for
               [ X or Y ]
      } 
     

使い分け

使い分けパターン 使用クラス 使い分けポイント
メソッドを引数に渡す方法 Func

<高階関数>

呼出先で、処理の切替を意識したくない時(呼び出し元で処理しておきたい時)に使える。
内部簡易メソッド delegate+Funcなど

<ラムダ>

インナーメソッドなので、外の呼出し元メソッドと変数を同じスコープで引き継ぎ、なおかつ内部処理のように書ける(さらにそのため、正式なメソッドのように引数をひとつひとつ渡さずに済む)。

 Java版はこちら

メソッドを引数として渡す方法

シーケンシャルに順番に処理されるのであれば、今までのやり方で不満はないが、その呼出先の真ん中で、ちょっと処理を変えたいけど、呼出先では意識したくない時などに重宝する。

(A→X→B ) の処理と  (A→Y→B)みたいな処理。 業務タスクを処理する塊があったとして、 夜の場合の処理の仕方のメソッドを渡すか、日中帯の処理のメソッドを渡すかして、処理を少し呼び出し先の処理中に切り替えたい時に使う。
これでX,Yの部分のコーディングがすっきりする。関数を引数としても渡せることがLispの生産性の強み でもあったし、人工知能で世代間を書こうとすると遺伝アルゴリズムをそのまま渡すこの方式が一番書きやすい。

3ステップ

1.呼び出される(引数として渡される)メソッドはいつもの様に普通に宣言する(X or Y)。

X or Y:

 private int add(int v1,int v2){ ... } のような関数が定義されている想定。

※X,Y同じ 関数名addでイメージしにくいのであれば、 Y: addではなく sub という関数で定義しておくとかでいい。

2.メソッドを引数に取る時の引数の定義を、メソッド型で宣言するだけ(A → B呼び出し時用)

B上の定義:

private static void totalDeal(Func<int, int, int> calc){ ... }

↑これは足し算/引き算のように、引数2つ取り、戻り値が int(3番目)のメソッドを calcという名前で受け付ける場合。

こぼれ話:
Func<int, int, int> がプログラマー一変倒だと引数と、戻り値の組み合わせ方が変な形に見えるかもしれないが、サブルーチンの呼出しだと再帰呼び出しが可能な stack型の構造をとるのが普通。コンパイラーやCPU、アセンブラーをやったことある人にはおなじみ。

ちなみにスタックオーバーフローは、引数やサブルーチンプログラムを桁数を超えて書き換え、戻り先などを変更させるため、暴走する。または悪用される。

2の補足:普通にもらった引数名をメソッドのように使う
B上
private static void totalDeal(Func<int, int, int> calc){

    calc(x1,x2);

}

JavaScriptなどのスクリプト言語で型が自由だと、よくありますよね。

3.呼び出す(A → B時)

A上の定義

totalDeal(add);

Funcで事足りると思うけれども、例えば戻り値をVoidにしたい場合、Actionを使う。

さらにに省エネ(Bの宣言部)

1ステップ目と3ステップ目をあわせる、無名メソッド(ラムダ式)の活用技がある。
3の時に、あらかじめ宣言するのではなく例のあの時の動作はこれね♪って、その場で作ったメソッドを渡しちゃうパターン。

totalDeal( (int v1 , int v2) => { return v1 + v2} ;);

内部簡易メソッド:無名メソッド

メソッドを呼ぶよりも、変化部分だけ引数で変えて渡すことができる引数の引き渡しを最小限にしてメソッドの恩恵を得られ、インナーメソッドのあるメソッドの中で整理しつつ名前をなしにできるところが楽できるポイント。インナーメソッドのようにメソッドの内部で微妙に繰り返したい時、メソッドを正式に作るより楽にできる。

使用の原則

3ステップ

1.delegateでClassを作る。メソッドのように型(返り値と引数)を宣言する。
delegate void MessageOut(string message);

※ delgate の前に public/private 等記載可能

MessageOutがこの場合の型/classとなる。

2.インスタンス作成のように、具体的にメソッドの内部を定義する。
MessageOut型で適当な名のaction インスタンスを作り、 具体的な動作内容は  括弧の中{}:Console ~~部分。

    MessageOut action = delegate(string msg)
    {
      Console.WriteLine(msg); // 匿名メソッドの内容
    };

※ このたびに作らなくても、すでに定義された 同じ型のメソッド(OutVoidStr とする)があれば、  MessageOut action = this.OutVoidStr; のようにも指定可能。

class PekePeke{

   public void OutVoidStr(String msg){
         ・・・
   }
}

JavaScriptなどのスクリプト言語で型が自由だと、よくありますよね。

3.先ほど実体化されたインスタンス経由で動作させる。

action("Hello! World!");

つまり、同じ型void/Stringであれば、このような感じで
何種類も必要に応じてその場で作ることが可能になります。
実際 delegateの宣言時は中身は指定していないですよね。

Action デリゲート (System)

他の定義例(メソッドっぽいdelegate先の型)

int 戻り値に、 int,intが引数の場合

delegate int sumTwo(int x, int y);

引数を増やしたり、片方だけ string や bool等にすることも可能です。

int(3つ目)が戻り値で、  (int, int)が引数の場合のFuncの定義

Func<int,int,int> func;

もう少し省エネ

クラスを宣言するのであれば、無名の意味がほぼ無いです。
C#では、System.Action<>の定義があり、さらに最初のdelegateを省略できます。

今回の 場合 1ステップ目を省略し、直接2ステップ目から定義できます。3ステップ目は変更ありません。
void , stringのケース

Action<string> action = delegate(string msg){
...
}

なぜAction型なのかというと引数と戻り値型に依存します。Javaでも同様です。これによりある程度静的コンパイル時に対応できます。

その他の一時メソッド作成例パターン:

目的の切り分け (例えば的な意味で)
事前定義されている型
戻り値あり、引数も自由自在:一番抽象的なメソッド Func
戻り値 Void実行するだけのメソッドAction
戻り値bool、引数1個:属性を断定する。
いわゆる isVoid()  , isValid() , isNumeric()的な使い方
Predicate
戻り値int 引数2つ:ソートなどで使う比較Comparison
戻り値任意、引数1つ:変換処理Converter
        // 戻り値void、string引数  
        Action<string> action = (string msg) =>   
        {  
            Console.WriteLine(msg);  
        };  
  
        // 戻り値int、string引数  
        Func<string, int> function = (value) =>  
        {  
            int k = someFunc(value);
            return k;  
        };  
  
        // 戻り値bool、string引数  
        Predicate<string> isMan = (seibetu) =>  
        {  
             return (seibetu == "男");
        };

ラムダ式

ラムダ式とは一般にメソッドの型を以下のように表現したもの。メソッドの中にメソッドがかける。

しかもスコープは元と連携できて変数をラムダ式外(呼び出し元のスコープのまま)のものをそのまま中で利用できる。引数で渡す必要もないため、すっきり。

(引数) => {
式の表現;
式の表現;
...
}

例:
(int x , int y) => {return x + y;};

これが、更に省略されて
(x,y)=> x+y
にもなる。

引数がない場合 最初のカッコも () => x+y; の形になる。

曖昧な表現になりそうだったらカッコ等で囲みましょう。

ラムダ式 (C# プログラミング ガイド)

ここまで来たら以下のプログラムが読めるようになると思います。
ヒント: => 前後を() {}で括る。

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

結果は奇数の数が求まります。 要素それぞれがラムダ式にわたり、条件に合えばカウントアップします。引数をどう返すか部分はラムダ式の中にあり、Countは配列の要素{5,4,1,,,}それぞれ 処理し、 この場で定義した nに渡して boolean値を返します。
Countはそれを受け処理します。 例えばtrueの場合 +1します。

つまり、条件をOK/NGと判断する実際のロジックをその場で作って、引数として渡しています。

引数を受けて一つ一つに分解しているのは Countですが、
カウントアップする条件は引数でもらうメソッドに任せてます。

Countの中を変えれば、柔軟に振る舞いを変えられるので、
関数渡し(C#の正式名称か知らないけど)であり、
これは一種のポリモーフィズムです。
sort関数/event listener関数などでよく見る形だと思います。

おまけ

ちなみに内部メソッド(ラムダ)のスコープはその上のメソッドのスコープも対象範囲になります(ラムダ式を読んでいるメソッド内のスコープも対象になる)。

関連ページリンク

アプリケーションの設定を保存しておく方法【C#】

デバッグや通知に便利なメッセージ通知方法の使い分け【C#】

座標操作のメソッドの使い分け【C#】

クリップボード操作方法の原則【C#】

アイコンイメージ3つの取得方法サンプルプログラム【C#】

キーエミュレート送信のまとめ【C#】【覚書メモ】

Delicious にシェア
Digg にシェア
reddit にシェア
LinkedIn にシェア
LINEで送る
email this
Pocket

7,206 views.



コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です