銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

JavaScriptで可変長引数を扱うモダンな書き方

f:id:takamints:20190125200451j:plain
photo credit: wuestenigel Domino effect via photopin (license)

みんなのコンピュータサイエンス
翔泳社 (2019-01-15)
売り上げランキング: 12,305

JavaScript 可変長引数」でググると「arguments」のことばっかり出てきますけど「今やJavaScriptの可変長引数は arguments を使わなくてもスッキリ書けますからねっ!」ってことを書いています。

歴史の古いJavaScriptには、初期の言語仕様が多く残されており、今でもある程度互換性を保っていますから、少々古い書き方でも大方うまく動いてくれます。 一方で、新たな構文や概念も多々導入されていますから、古くからJavaScriptを使っている人が最新のコードを見ると「なんじゃい?この書き方は?」となるかもしれません。 しかし、安全性や利便性が向上しているのは確かですから、少しづつでもモダンなJavaScriptを習得したいものですね。

ということで、ここではES2015(ES6)以降のJavaScriptでスッキリ書ける「可変長引数」と「レストパラメータ」について書いております。

目次

可変長引数を扱う古い方法:arguments を使用する

まずは古い方法から。arguments はよく知ってるよって人は、このセクションは読み飛ばしてもらって結構ですよ。

JavaScriptの関数内では arguments というオブジェクトが、実行時に指定されたパラメータをすべて保持しています。

arguments は配列(Array)のように扱えるArgumentクラスのオブジェクトです。

引数以外にも呼び出し元や呼び出されている関数自体なども保持しています(ただし”use strict”; なstrictモードでは使えないものがあります。)

argumentsの使い方

引数に関しての使い方は、ほぼ配列です。

以下の関数fooは、受け取った引数をコンソールへ出力するだけの関数です。

function foo() {
    console.log("引数の数=" + arguments.length);
    for(let i = 0; i < arguments.length; i++) {
        console.log("[" + i + "]=" + arguments[i]);
    }
}
foo("A");
    // 引数の数=1
    // [0]=A
foo(1,2,3);
    // 引数の数=3
    // [0]=1
    // [1]=2
    // [2]=3

argumentsは明示的な引数宣言と無関係

関数に明示的な引数リストが宣言されていても、 arguments はそれら全てを含んでいます。

function foo(param0) {
    console.log("引数の数=" + arguments.length);
    for(let i = 0; i < arguments.length; i++) {
        console.log("[" + i + "]=" + arguments[i]);
    }
    console.log("param0=" + param0);
}
foo("A");
    // 引数の数=1
    // [0]=A
    // param0=A
foo(1,2,3);
    // 引数の数=3
    // [0]=1
    // [1]=2
    // [2]=3
    // param0=1

argumentsは配列ほど便利ではない

argumentsは配列っぽく使えますが、Arrayクラスの多くのメソッドが使えません。

これでは不便なので、以下のように一旦配列に変換するのが定石的なコードです。

function foo() {
    const vargs = Array.from(arguments);
    console.log("引数の数=" + vargs.length);
    vargs.forEach( function(item, index) {
        console.log("[" + index + "]=" + item);
    });
}

明示的な引数宣言がある場合は、途中からだけ変換したり。

function foo(bar,baz) {
    const vargs = Array.from(arguments).slice(2);
    console.log("引数の数=" + vargs.length);
    vargs.forEach( function(item, index) {
        console.log("[" + index + "]=" + item);
    });
}

arguments はアロー関数で使えない

アロー関数の中では arguments を使用できません。

つまり、アロー関数で可変長引数を受け取るには新しい方法でなければなりません。

argumentsの問題点まとめ

ここまで長々と書いてきましたが、argumentsの問題点は、アロー関数で使えないという致命的な点に加えて、可読性や保守性を損なっている点です。

上に書いたように歴史的に定石コードが使われてきましたが、それでも「単純だが見つけにくいバグ」が入る隙間ができています(これが一番の困り者)。

こういったコードの隙間はできる限りふさいでおくほうが安全です。

そういう意味でも、新しい方法が推奨されます。

可変長引数を扱う新しい方法:レストパラメータの使用

最新のJavaScript(ES6以降)では可変長引数をレストパラメータ(rest parameters)で宣言します。

レストパラメータとは、関数の最後の引数が ...args という形式(ドット3つに変数名)になっているものです (レスト = rest =「残り」ですので、「残りのパラメータ」というような意味でしょう)。

const foo = (param0, ...args) => {
    console.log("可変長引数の数=" + args.length);
    args.forEach( function(item, index) {
        console.log("[" + index + "]=" + item);
    });
    console.log("param0=" + param0);
};
foo("A");
    // 可変長引数の数=0
    // param0=A
foo(1,2,3);
    // 可変長引数の数=2
    // [0]=2
    // [1]=3
    // param0=1

ここでargsは単なるパラメータの名前ですから別の名前(例えば ...list )でも構いません。

レストパラメータは配列(Arrayオブジェクト)であり、この位置以降に指定された引数の値をすべて保持しています。

名前付きの可変長引数:スプレッド構文と組み合わせて

名前付きの可変長引数を使用できます。レストパラメータは配列ですが「スプレッド構文」との合わせ技で可能になります。

以下の例の第二引数が名前付きの可変長引数(...[va0,va1,va2]の部分)です。つまりこれ全体がレストパラメータで、[va0,va1,va2] の部分がスプレッド構文なんですね。

const foo = (param0, ...[va0,va1,va2]) => {
    va0=va0||0;
    va1=va1||0;
    va2=va2||0;
    const result = va0+va1+va2;
    console.log(`${param0}=${va0}+${va1}+${va2}=${result}`);
    return result;
};
foo("A", 1,2,3);
    // A=1+2+3=6
foo("A", 1,2);
    // A=1+2+0=3

指定しなかったところはundefinedになるようですね。

スプレッド構文というのは配列やオブジェクトを個々の要素に分解する構文なんですが、上の部分を見ると何となく理解できますでしょうか。

スプレッド構文の詳細は以下を参照してください。

ちょっとややこしいですけど、名前付きの可変長引数の最後の変数をレストパラメータにできます。

以下の例では、va2がそのように指定されています。この場合、fooに引数を与えなくても、va2は必ず配列になります。

const foo = (param0, ...[va0,va1,...va2]) => {

まとめ

  • argumentsは配列みたいなオブジェクトですが配列ほどには便利ではありません。レストパラメータは配列ですからそれなりに便利。
  • arguments は第一引数からすべてを保持しています、レストパラメータは宣言位置以降の値を保持します。
  • arguments はアロー関数では使用できませんがレストパラメータは使用可能(もちろん通常の function でもOK)。
  • argumentsはローカル変数ですが、ローカル関数の内部からはアクセスできません(ローカル関数自身のargumentsに置き換わります)。
  • レストパラメータはスプレッド構文と組み合わせることで可変長引数に名前を付けられます。

古いからといって間違いではありませんから、既存の動作しているコードを変更する必要などはありません (もしも変更するならば、ユニットテストを用意して取り掛かりましょう)。

しかし今から書くコードは(arguments に限らず)できるかぎり最新の手法を使うべきですね。

WebWorkerを手軽に扱うTransWorker v1.1でSharedWorkerが利用可能になりました!

f:id:takamints:20160131175631p:plain
photo credit: Pallet via photopin (license)

JavaScript において、メソッド呼び出し感覚で手軽に WebWorker によるマルチスレッドを扱える npmモジュール TransWorker を v1.1.0 に更新しました。

従来 DedicatedWorker にしか対応していなかったため、ワーカースレッドを生成したメインスレッドからしか扱えませんでしたが、 複数スレッドで共有できる SharedWorker が使えるようなり、ブラウザの異なるタブやウィンドウ間でワーカースレッドを共有できます。

その他、ちょっとした機能増強や不具合修正なども合わせて対応しています。

このページには、今回の変更点の概要、WebWorkerの基本事項、TransWorkerの使い方などを書いています。その他詳細はnpmのREADMEを参照してみてくださいね。

www.npmjs.com


目次

関連記事:

takamints.hatenablog.jp


TransWorker 更新内容 v1.0 => v1.1

SharedWorkerに対応【機能追加】

既に上で書いていますが、これまで DedicatedWorker にしか対応していなかったため、生成元のスレッドからしかワーカースレッドを扱えませんでした。

v1.1からは、複数スレッド間で共有できる SharedWorker が使えるようなったので、ブラウザの異なるタブやウィンドウ間でワーカースレッドを共有できます。

ワーカーからの通知のハンドラを後付け可能に【機能追加】

メインスレッド側でTransWorkerのインスタンスを生成するときに、ワーカースレッドからの通知を受け取るハンドラーをまとめて指定する形式をとっていましたが、インスタンス生成後に、個々の通知に対するハンドラーを設定できるように機能を追加しました(TransWorker.subscribe)。

メソッド呼び出し時の空のコールバックを不要に【不具合修正】

従来、メソッドの戻り値が無くてコールバックが不要な場合にでも、空のコールバック(()=>{} 等)を指定しなければいけなかったのですが、あまりにも不便でバグの元になっていたので修正しました。

引数リストの最後に関数オブジェクトが指定されていない場合は、コールバックは不要と判定します。 このチェックのため、動作速度に若干の影響があるかもしれません。 ほぼ体感できないレベルだとは思いますが、何らかの対策を考えたいです。

WebWorker について

※ここ、知ってる人は読み飛ばしてもらって結構ですよ。

WebWorkerはブラウザ上で動作するJavaScriptをマルチスレッド化するための仕組みですね。 メインスレッド(UIスレッド)からワーカースレッド(独立した実行コンテキスト)を生成し、スレッド間メッセージで通信できます。

DedicatedWorker と SharedWorker

ワーカースレッドには DedicatedWorker と SharedWorker という2つのワーカーが存在します(ServiceWorkerは別扱い)。

2つの違いは、その名前があらわしており "Dedicated" =「占有される」、"Shared" =「共有される」ということです。

つまり、DedicatedWorker は生成元のスレッドとしか通信できないのですが、SharedWorker は複数のスレッド間で共有され、各スレッドと通信可能なワーカーです。

例えばワーカースレッドで動作しているアプリケーションのコアロジックをブラウザの複数のタブやウィンドウから利用できるようになるわけです。

SharedWorkerの対応状況と制限事項

Chromeだけ?

せっかく SharedWorker に対応したのに、ざっと確認したところ、2018年12月現在 SharedWorkerがまともに動いているのは Chrome だけっぽいのです。 FirefoxではDedicatdWorkerと同様の動きをしており、EdgeやIE11では「SharedWorkerが見つかりません」と、にべもない。 SafariOpera は未確認です。

ChromeのSharedWorkerではコンソール出力が使えない?

唯一SharedWorkerに対応しているように見える Chrome で、SharedWorkerの実行コンテキスト内から console.log が使えませんでした。 エラーにはなりませんが、何も出力されません(DedicatedWorkerでは出力されます。ただし大量に出力するとフリーズしやすかった)。

このため、後述の TransWorker で、SharedWorkerを使用している場合は、メインスレッド側へ転送して出力するようにしています。

TransWorkerの概要とサンプルコード

TransWorker は、WebWorker を手軽に利用するための npm モジュールです。

ワーカースレッドで実行したい処理をクラスのインスタンスメソッドとして定義 しておけば、メソッド呼び出しと同様の手順でマルチスレッドを利用できます。 スレッド間メッセージを意識する必要はなく、普通にJavaScriptのクラスを実装する感覚で実装できます。

メソッド呼び出しをスレッド間メッセージに変換

TransWorkerは与えられたクラスのインターフェースを読み取って、メソッド呼び出しをスレッド間のメッセージ通信に変換 します。

全てのメソッドは非同期メソッドとなり、各メソッドの戻り値はコールバック関数で受け取れます。

以下に最もシンプルなサンプルを示します。もう少し複雑なサンプルはコチラにあります。

foo-bar.js

以下のクラス はひとつの値を保持するクラスで、値を設定するメソッドと、読み取るメソッドが定義されています。 これら2つのメソッドはワーカースレッドで実行されます(実際にはコンストラクタもですね)。 特別にマルチスレッドで処理したいことではありませんがサンプルなのでご容赦を。

function FooBar() {
    this._value = null;
}
FooBar.prototype.setValue = function(value) {
    this._value = value;
};
FooBar.prototype.getValue = function() {
    return this._value;
};

index.html

WEBページでは、TransWorker、FooBarクラスと、次に説明するメインスレッド側のスクリプトを読み込んでいます。 バンドラーでまとめる場合は別な形式になると思いますが、とりあえずサンプルはシンプルに書いています。

<html>
    <script src="path/to/the/transworker.js"></script>
    <script src="foo-bar.js"></script>
    <script src="main-thread.js"></script>
</html>

main-thread.js

メインスレッド側のスクリプトです。

FooBarのコンストラクタを与えてTransWorkerを生成し、それを通じてFooBarのメソッドを呼び出します。 この呼び出しはTransWorkerによってスレッド間通信に変換され、ワーカースレッド側のFooBarのインスタンスメソッドを呼び出します。 この戻り値は、コールバックで受け取ります。

//メインスレッド側ではワーカースクリプトとクラスの
//プロトタイプを参照して生成する
const fooBarWorker = TransWorker.createInvoker(
    "./worker-thread.js", FooBar);

fooBarWorker.setValue("Baz"); //戻り値が無いのでコールバック不要

//FooBar.getValueの戻り値はコールバックで受け取る
fooBarWorker.getValue(value => {
    console.log(value); // > Baz
});

woker-thread.js

ワーカースレッド側のスクリプトです。FooBarのインスタンスを指定してTransWorkerのワーカー側インスタンスを生成しています。

importScripts(
    "path/to/the/transworker",
    "./foo-bar.js");

//ワーカースレッド側ではFooBarのインスタンスを利用
TransWorker.createWorker(new FooBar());

ワーカースレッド側からの通知

また、ワーカースレッド側から(メソッド呼び出しの戻り値ではなく)能動的な通知も行えます(TransWorker.postNotify)。 SharedWorkerを使っている場合、この通知は共有するすべてのスレッドへブロードキャストされます。 ただし、ワーカーのどの通知を受け取るかは生成元または共有元の各スレッドで決定できます(TransWorker.subscribe)。

以下の例は上のサンプルを一部書き換えたものです。

foo-bar.js

function FooBar() {
    this._value = null;
}

FooBar.prototype.setValue = function(value) {
    this._value = value;

    //通知を発行する。
    this._transworker.postNotify("valueUpdated", value);
        //_transworkerフィールドはTransWorkerのファクトリが
        //注入する、このインスタンスを保持するTransWorkerの
        //インスタンスです。
};

FooBar.prototype.getValue = function() {
    return this._value;
};

main-thread.js

const fooBarWorker = TransWorker.createInvoker(
    "./worker-thread.js", FooBar);

// "valueUpdated"通知を受け取る
fooBarWorker.subscribe("valueUpdated", value => {
    console.log(`valueUpdated ${value}`);
});

//valueUpdated通知が発行される
fooBarWorker.setValue("Baz");

API

メインスレッド向け

ワーカースレッド向け

リンク

ここに記述していること以外の詳細、モジュール自体の使い方や、サンプルプログラムなどについては、以下 npm や GitHubリポジトリのREADME、またGitHub.IoのAPIドキュメントに記述してあります。

祝!SHARP MZ-700シリーズ発売36周年!な思い出話とエミュレータのお話です

f:id:takamints:20181115104532p:plain

自分にとっての最初のコンピュータ SHARP の 8ビットマイコン MZ-700 シリーズが 1982年11月15日に発売されて、今日でちょうど36年経ちました。 おめでたいので、当時からの思い出話などつらつら書いてお祝いしようと思います。

当時は、まさか36年後に、こんなお祝いしてるだなんて思ってもいませんでした。 当たり前ですけど当時はインターネットも携帯電話もハードディスクも普及していません。 ボクらにとってのメジャーな媒体はカセットテープでしたから!10分かけてゲームをロードしてたんだよー(笑) こんなに便利な世の中になったのは、みんなのおかげ。いろんなものに感謝・感激しておきたいです!おめでたい!

本日も、はてなブログで毎年MZ-700の発売記念をお祝いしていらっしゃるすぎもとさんのブログの記事を思わずTwitterでシェアしたら、たくさんの「いいね」をもらって、リツイートしていただきました。

なんだかすぎもとさんのふんどしで相撲を取ってるみたいで申し訳ない気分でもありますので、ご本人のツイートをRT。かのブログはすぎもとさんのブログですのでー。

なにより平成最後のこの秋に、MZ-700を覚えている人がたくさんいるってことが、うれしい・楽しい・素晴らしいですね!

レジェンドパソコンゲーム80年代記
佐々木潤 レトロPCゲーム愛好会
総合科学出版
売り上げランキング: 239,995

初めてのマイコンMZ-700との出会い

今から36年前。ワタシが中学二年の12月。父がMZ-731を買ってきました。プロッタプリンタがついている奴ですね。

父は仕事で使うために買ったのだとおもいますが、結局兄と私がほぼ占有することになっていました。

そんなこんなで自分にとって一番印象深い過去のマシンは未だにコレがトップです。

MZ-700のおかげ様

中学生の時にほぼ毎日プログラムリストとにらめっこしたおかげで、BASICやZ80アセンブリ言語を覚えました。

一時期マイコンからは離れていましたが、数年後、成人してからソフトウェア企業へもぐりこむときに役立ちました。

その時まで、まさかコンピュータのプログラムを書くことが仕事になるとか思っていなかったんですよね。自分にとっては遊びでしたから。

その後ソフトウェアのエンジニアとして大成功こそしていませんが、紆余曲折とかありつつも、それなりに幸せに、それなりに楽しく暮らせているのも、このマシンのおかげといって過言ではないと言い切りたい。

フルJavaScriptエミュレータ

ここ数年は、あくまでも趣味の範疇でブラウザで動くJavaScriptによるエミュレータを作って公開しております。 業務上JavaScriptを多用しており、最近のブラウザならZ80のエミュレーションとか平気でできるのではないだろか?と思ったのがきっかけでした。

とはいえ、そんなに簡単にはいかず。約1年半で「MZ-700のエミュレーションやってます」と胸を張って言える状態まで持っていって、つい3か月ほど前にv1.0をリリースした次第。

そのうち手を入れますのでゴメンナサイ

しかしこのあとまた放置。本業のほうがなかなかややこしい状況というのもあって、なかなか手が付けられません。

10月にリリースされた ChromeのVer.70で、「Audio API の使用はユーザー操作によって許可しないといけません」てな制限がかかるようになったのですが、これも手付かず。 ページを開いていきなり音が出るのを防ぐためなんですね。 この制限、4月か5月に一度リリースされてましたが上手く周知されていなかったようで世界中から猛反対に遭いGoogleさんが引っ込めちゃった。 Googleさんが引っ込める前に慌てて対策していたのですが、引っ込められて余計にややこしくなったので、こちらもロールバックしてしまい、そのコミットがどっかいっちゃいましたねえ。まいりました。

takamin.github.io

キャラグラの世界

先日、前述のすぎもとさんのブログで、キャラグラのMZTファイルが公開されていました(MZTはMZシリーズで読み込み可能なファイル形式です)。

30th700.hatenablog.com

せっかくなので、これをダウンロードして拙エミュレータで実行してみましたが、動きません。

メモリアクセスの関連でエラーになっていましたので、こちら側の不具合ですね。

この良き日にバグを見つけて残念ですけど、今から泣きながらデバッグしようと思います。

てな感じで普段の記事とは雰囲気が違っていますが、自分にとって、とっても大切な日でしたよってことで、それでは!

リンク

30th700.hatenablog.com

30th700.hatenablog.com

30th700.hatenablog.com

takamin.github.io

非同期処理の直列化:今やArray.reduceを使わなくてもできますよね

f:id:takamints:20181114220424j:plain
photo credit: hans-johnson 700-7000 Series_1 via photopin (license)

非同期処理の直列化とは「複数の非同期処理を、順番に実行する処理」のことです。非同期処理の順次実行や逐次実行とも呼ばれます。 処理速度は、並列処理よりも遅いのですが、処理順が重要であったり、先に実行した非同期処理の結果を次の非同期処理に使用する場合に必要になります。

JavaScriptでの非同期処理の直列化のやり方を検索すると「Array.reduce を使えばできる」とよく出てきます。

しかしアレ、そんなに便利ですかね?

コードが直感的じゃないし、ぱっと見ナニやってるのか分かりにくい。処理効率もそれほど良くなさそうです。

なんかもう「他のやり方ではできないよ」って誤解しそうな勢いですが、そうじゃない。 ES2017(ES8)以降のJavaScriptならもっとシンプルに書けますよね。

確かに「目的外使用」的で、軽く目からうろこが落ちて、「なるほどイイね!」と思う気持ちは理解できるのですが、それよか「誰にとっても直感的でわかりやすいコードを書く」ほうが重要ではないかと思ってます。

実際MDNでも、 reduceを使った直列化のコード が紹介されています。でも「こうすればできる」は「こうするべき」ではないですよね。

Vue.js入門 基礎から実践アプリケーション開発まで
川口 和也 喜多 啓介 野田 陽平 手島 拓也 片山 真也
技術評論社
売り上げランキング: 1,741

reduce使わずどうするか

じゃあreduce使わずにどうするの?っていうと、単純にfor ループで async / await を使えば普通に書けますfor~of 文ならもっとラク。reduce よりも明らかに簡潔です。

async/awaitが使えないならreduceを使うしかなさそうですが、そんなレガシーな環境で動かしたいなら Babel で変換すればよろしいのです。

※ ちなみに、Array.forEachでループを回すと並列処理になってしまいますので使えません。

for~of で非同期処理を直列化するコード

以下、ちょっと長くなっていますが、MDNのreduceのページで紹介されているreduceを使った直列化のコードfor~of を使った同等の処理を追加しました。

/**
 * for~of を使ってPromise配列をチェインしながら実行する。
 * (下のreduceバージョンと比べて如何に簡潔か確認)
 *
 * @async
 * @param {array} arr - Promise配列
 * @return {Object} Promiseオブジェクトを返す
 */
async function runPromiseInSequenseByForOf(arr) {
  let res;
  for(const currentPromise of arr) {
      res = await currentPromise(res);
  }
  return res;
}

/**
 * reduce を使ってPromise配列をチェインしながら実行する。
 *
 * @param {array} arr - Promise配列
 * @return {Object} Promiseオブジェクトを返す
 */
function runPromiseInSequense(arr) {
  return arr.reduce((promiseChain, currentPromise) => {
    return promiseChain.then((chainedResult) => {
      return currentPromise(chainedResult)
        .then((res) => res)
    })
  }, Promise.resolve());
}

// promise function 1
function p1() {
  return new Promise((resolve, reject) => {
    resolve(5);
  });
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// promise function 3
function p3(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 3);
  });
}

const promiseArr = [p1, p2, p3];

// Reduceバージョンで直列化
runPromiseInSequense(promiseArr)
  .then((res) => {
    console.log(res);   // 30
  });

// For~of バージョンで直列化
runPromiseInSequenseByForOf(promiseArr)
  .then((res) => {
    console.log(res);   // 30(結果は同じ)
  });

これ見て、どっちが良いかって考えると、どう考えても for~of のほうが良いと思うんですよね。

ただ、関数型プログラミングに傾倒している人にとってはletで宣言した変数resを逐次更新するところが気持ち悪いのかもしれません。

C#のActionクラスとFuncクラスを理解する

f:id:takamints:20181112124832j:plain
photo credit: ARMLE Action ! via photopin (license)

C#でメソッドや関数を表す 2つのクラス Action と Func について説明します (全くの余談ですが、C言語的には関数ポインタ、JavaScript的にはFunctionオブジェクトに相当するものです)。

2つの違いは戻り値の有無。戻り値なしが Action で、戻り値ありが Func です。

どちらもジェネリックパラメータとして関数のパラメータリストを指定できるジェネリッククラスです。 ジェネリックパラメータを指定しない場合は引数無しの関数を表すことになります。

以降、それぞれの詳細な使い方について説明します。

※ この記事は、もともと「C#のラムダ式はAction・Funcと一緒に理解を深めるとヨロシイようで」で書いていた内容です。長ったらしいのでこちらに独立させました。

目次

Action クラス

ACTION
ACTION
posted with amazlet at 16.09.22
B’z
VERMILLION RECORDS(J)(M) (2007-12-05)
売り上げランキング: 22,294

Actionクラスは「戻り値のない処理を記述するためのクラス」です。

Actionだけなら引数もなし。

// 引数無しのActionをラムダ式で生成
Action action = () => {
    foo.bar();
};

引数が必要ならAction<引数の型リスト>を使用します。

例えば、

//引数を取るActionをラムダ式で生成
Action<string, int> action = (name, age) => {
    Console.WriteLine(string.Format(
        "{0} is {1} years old.", name, age));
};

てな感じです。

このように、インスタンスを作ること自体は難しいことではありませんが、誰かが作った既存のメソッドがActionクラスの引数を要求している場合、この逆の考え方、つまり、そのメソッドの定義を見て「何を与えればよいのか」をきちんと理解する必要がありますね。

覚えること ☞「Actionクラスは戻り値のない関数。ジェネリックパラメータは引数リストの型を表しています。」

Func<TResult>クラス

グレイト・ヒッツ
グレイト・ヒッツ
posted with amazlet at 16.09.22
T.レックス
インペリアルレコード (2005-05-25)
売り上げランキング: 3,577

Funcは戻り値があるメソッドを表します。戻り値がboolで、引数のないFuncは、以下のように記述します。

//戻り値がboolのFuncをラムダで生成
Func<bool> func = () => {
    return true;
};

引数が必要な場合は、Actionと同じくジェネリックパラメータで型を指定します。

//戻り値がboolで引数付きのFuncをラムダで生成
Func<string, int, bool> isAround50 = (name, age) =>
    Console.WriteLine(string.Format(
        "{0} is {1} years old.", name, age));
    if(45 <= age && age < 55) {
        return true;
    }
    return false;
};

これがたとえば、メソッドのパラメータだとしても、考え方は同じで、以下のように書くわけです。

Task<bool> task = new Task<bool>(() =>
{
    return execute.Invoke();
});
chainedTask._task.Start();

Lambda式は、ActionかFuncクラスのインスタンスを生成していると考えられます。 このため、Lambda式だけを見て、どちらの型なのか推測するクセをつけておくと、混乱が少なくなる気がします。 ようするにしっかり理解しておきましょうということですが。

上の例では、Task<bool>のコンストラクタの第一引数の型は Func<bool> だと推測できますね。

そのほか無駄話など

Func<void>ではダメなんですか?

個人的な好みとしてですが、戻り値の有無でクラスを分けずに、Func<void>を認めて、Actionクラスはなくてよいでしょと思っています。 しかし、そもそもジェネリックの型パラメータにvoidは無理なのかもしれませんね。 VBでもFunctionとSubに分かれているし、マイクロソフトさんは昔から分けたい派なのかと思っていたけど、言語的制約なのかもしれません。