銀の弾丸

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

C#のラムダ式はAction・Funcと一緒に理解を深めるとヨロシイようで

年に一度のパートタイム・シーシャーパー(C#erと書くらしいですね)ですが、今年は少し期間が長く、去年よりは深い仲になれそな予感がしてます♪

とはいえパートタイム・シーシャーパー(もういい)なので、最新の動向を追いかけるのには四苦八苦。

てことで、今まで中途半端にしか理解していなかった Action、Func、ラムダ式などについて、現場で実際に使って理解したことを書いておきます。

f:id:takamints:20160922160154p:plain
photo credit: Imperial Shuttle via photopin (license)

実際この記事、専門の人にとっては「何だ?今さら?」的だと思います。そこんところはお手柔らかに。

ちなみに現場では、MVVM警察に怯えております。

「おいそこ!なんでクリックイベント拾ってるんだ!今すぐコマンド使いなさい!」みたいなビクビクもん。

いくつになってもお勉強です(泣

C#ラムダ式

C#逆引きレシピ
C#逆引きレシピ
posted with amazlet at 16.09.22
arton
翔泳社
売り上げランキング: 97,981

ラムダ式の記法的には、以下のような感じ。引数がひとつなら丸かっこは不要とか、波かっこの中身が単一の文なら(複文でないなら)波かっこは要らないとか、いろいろあるんだけど、基本はこちらでOKです。

(name,age) => {
    Console.WriteLine(
        string.Format(
            "{0} is {1} years old",
            name, age));
};

型とか指定されていないし、戻り値ってありなの?無しなの?どうでもいいの?てな具合に、全く情緒が安定しない代物ですが・・・

これは無名関数ではありません

曖昧な理解で、即時関数的な使い方をしようとして、「あれ?なんか違う」と混乱。 コピペで、どうにか動かせるけど「ここでは、なぜ、そう書かなければならないのか」まで、スッキリ理解できていないっていう状態。

C#ラムダ式って「無名関数でしょ?」的な思い込みがあったんですね。 でもそれは間違い。ラムダ式=無名関数ではありません。

ラムダ式というランタイムオブジェクトは、無い

結局、ラムダ式を問題なく自由に使えるようになったのは、ActionクラスとFuncクラスを理解して、一緒に使うものだと認識してからでした。

それまでは、ラムダ式単体で見て「型が明示されていないのに、どうコンパイルされて実行されているのだ?」と不審に思っていました。 しかし、その、引数の数や型、戻り値の型など、欠落している情報は一緒に使われている ActionFunc、またはデリゲートで明示されているってことですね。

ラムダ式はそれらのオブジェクトを生成するために使われるんだけど、ランタイムに何らかのオブジェクトとして存在しているわけではないということです。

単なる記法、シンタックス・シュガーだよ

つまり「ラムダ式は、デリゲートやActionやFuncを記述するためのシンタックス・シュガーであって、それ自体はオブジェクトでもなんでもなく、単なる記法」というわけです。

その証拠に、JavaScriptの即時関数みたいなのは、C#ラムダ式だけでは実装不可

//JavaScriptの即時関数呼び出し
var a = 1;
(function(b) {
    a += b;
}(2));
console.log("a:", a);// "a: 3"

以下のように、一旦Actionインスタンスを作ってからでないと実行できない。Actionで第一引数がintであることを明示していますね。(敢えて似せて書いています。丸かっことかね)

int a = 1;
(new Action<int>(b => {
    a += b;
})(2));
Console.WriteLine(string.Format("a: {0}", a));

JavaScriptでの無名関数は関数オブジェクトだけど、C#ラムダ式に直接対応するオブジェクトはないってこと。 ちなみにJavaラムダ式は無名クラスのインスタンスだということです。

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));
};

てな感じ。

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;
};
//戻り値が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();

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

そのほか無駄話など

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

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

シンタックス・シュガー

シンタックス・シュガーって「糖衣構文」と訳されると知って軽くショックを受けている。

その昔、Perlラクダ本で「構文糖」と目にして以来、そのまま使っていたんだけど、21世紀の現代的には「なにそれおじさん」なんだろうか?

セルの範囲に入力された最も右の値を得るにはワークシート関数「MATCH」を使えば良いらしい

MATCHと一緒にINDEXとMAXも使います。

何の話かってエクセルです。セルの中の計算式。 エクセルのワークシート関数はたくさんあって、かなり強力。統計用の関数とか、行列演算さえできてしまう。 さらに、今や Google Driveスプレッドシートなど、他の表計算ソフトでも使えるので、覚えておいて損はない。いろんな局面で役に立ちます。

しかし、かれこれ20年以上使い続けているのに、今になって新たなワークシート関数を知って感心するとは思ってもいませんでした。

いくつになってもお勉強ですニャ

f:id:takamints:20160831214321p:plain
photo credit: Bailey holding a mug via photopin (license)

事の発端:進捗管理しなくちゃだっ!

先日、個人作業の進捗管理のシートを軽い気持ちで作っていたんですね。

縦方向に作業項目を並べて、横方向には当日から締め切りまでの日付が並ぶ。各項目の進捗状況を日毎に記録するものです。

期限内に全作業がきちんと終了するよう自己管理。日々の状況を把握して、対策打ったり、あきらめたり(笑)、ふさぎ込んだり(!?)ってな使い方です。

客観的マネージメントには数値管理が必要ですし、なにより残工数とか表示しちゃって、予測曲線プロットしたりと、もうデータフェチにはたまらない喜びなわけですよ。理解できないかもしれないけど。

一番右が欲しいのです

作業項目は、1人日(いちにんにち = 一人の人が一日でできる作業)程度に分割しておき、各項目行の日毎のセルに0.5=「今やってます」とか、1.0=「完了した」とか書いていく。

でも、完了した日以降には(戻りが発生しなければ)同じ数値を入れたくない。手間だし、表としても見にくいし。

なので、各項目の最新の進捗を一覧として見たい場合、各行の中で、入力されてる最も右の値を得る必要があるわけです。

過去にはVBAのマクロでやってた。でも入力時のトリガーで再計算とか結構遅いし、別のシートへ展開しにくい。 データ管理用のシートに分けたりしてみましたが、行や列の挿入で簡単に破たんする。

だから本来、計算式でやりたいが、そのやり方を知らなかった。

過去の自分にググレカス

で、この度ググってみたら以下のサイトが見つかった。

EXCELで、範囲指定した一番右の数値(セル)を返す関数ってありますか?…-Yahoo!知恵袋
detail.chiebukuro.yahoo.co.jp

というか他にもゴロゴロ出てきますやん。しかもそれぞれ、結構古くて2009年とか、7年前の情報です。

なんで今まで検索しなかった?と不思議でしたが、よくよく思い出してみると、ちょうどワタシが管理業務から逃れた離れることになった時期。 当時、このような管理面のことを、(一時的に)ちっとも考えなくなっていたんだな。ダメですね。

MATCHで~す

マッチ箱の脳(AI)―使える人工知能のお話
森川幸人 (2014-01-05)
売り上げランキング: 1,343
モノワイヤレス TWE-001L-DIC-WA TWE-Lite DIPシリーズ 端子付き(マッチ棒アンテナ)
モノワイヤレス(Mono Wireless)
売り上げランキング: 26,668

まあ、とにかく、そこで紹介されているのは、以下のような計算式でした。

セル範囲A1:E1に入力されてる最も右の値を得る:

= INDEX ( A1:E1, MATCH( MAX(A1:E1) + 1, A1:E1, 1 ))

INDEX関数

INDEX関数は、セル範囲からインデックスを指定して値を得る関数です。 第一引数がセルの範囲で、第二引数がインデックス。上の式ではA1:E1がセル範囲。MATCH( ... )がインデックスですね。

MATCH関数

で、INDEXの第二引数に指定されてるMATCH関数は、一次元のセル範囲(つまり1行か1列)から検索値にマッチする値が入力されたセルの位置(インデックス)を返す関数です。第一引数が検索値、第二引数がセルの範囲、第三引数が検索の型。

MAX関数

その名の通り、範囲内から最大値を得る関数ですね。

ちょっと待って、なんで最大値を探すのだ?

しかしちょっと待ってくださいよ、上の例では、検索値が、MAX(A1:E1)+1となっていて、セル範囲内の最大値+1となっているではないですか。これ検索してもダメなんじゃ?!と思ったのですが、どうやらミソは第三引数の検索の型。検索の型が1だと、検索値以下の最大値にヒットするのだとか。(だから本来+1する必要はないのだと思います)。しかも省略時のデフォルトが1なので省略してもよいみたい。

まだワカラン。一番右が最大だとは限らんのでは?

しかしまだ、納得できない。釈然としない。最大値の位置を知っても、それが一番右にあるとは限らないのだから、これでは目的の値は得られないのでは?と疑いましたが、なんとMATCH関数の説明に、セル範囲内のデータは昇順にソートされている前提と書かれているんですね。

エクセルのヘルプでは、たまにこういった軽く意味不明の記述がありまして、見るたびに「なんちゅう都合のいい仕様?!www」と嘲笑とか憤慨を覚えていました。

しかし、どうやら、この前提のおかげで、最後まで検索してくれるよう。こういう目的で使いたいから、こんな妙な前提を入れているのかなとか思ったりもしたがはて?。

サンプル(Googleスプレッドシート

以下に、Googleスプレッドで作ったサンプルを埋め込んでいますが計算式が読めないのでこちらからどうぞ。

最近気づいたVisualStudio 2015 C# で便利に使える5つの機能

全国的に梅雨も明け、本格的な夏ですね。 自分的には(仕事で)年に一度の恒例のVisualStudioシーズン・イン。 昨年までは冬場が多く、期間は長くても2か月程度。 しかし今年は 6月初めから徐々に動き出して、7月以降に本格化。 期間はトータル4、5か月になりそうです。 終わるころには秋ですね。

例年そんなインターバルでやっているので、VisualStudioやC#の、最新開発状況(環境やプログラミングスタイルなど)になかなか追従できていないのですが、今回は多少期間が長いため、視野がちょっとだけ広くなったか「あ、こんなことができるんだあ」とか「こんな風に書けるのねー」みたいなことが何度かありましたので書いておきます。

あくまでも、自分が知らなくて最近気が付いたってことですので、皆さんご存知なことばかりかも。 しかし、いくつになってもお勉強。新知識には興奮します。

f:id:takamints:20160730125037p:plain

メニュー

  1. プロパティ名を指定せずOnPropertyChangedを呼ぶ
  2. nullチェックの簡略記法
  3. 自動実装プロパティの初期値設定
  4. 読み出し専用プロパティを自動実装
  5. プロパティやメソッドのスニペットを挿入する

1. プロパティ名を指定せずOnPropertyChangedを呼ぶ

INotifyPropertyChangedを実装したクラスのプロパティに値を設定されたとき、PropertyChangedイベントを発生させますが、これまで以下のようにプロパティ名を文字列で指定して、OnPropertyChangedメソッドを呼び出していました。律儀にね。

using System.ComponentModel;
namespace Application {
  class ViewModel: INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged(string name) {
      if(PropertyChanged != null) {
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
      }
    }
    private int _propFoo;
    public int PropFoo {
      get { return _propFoo; }
      set {
        _propFoo = value;
        OnPropertyChanged("PropFoo");
      }
    }
  }
}

ところが、プロパティが増えると、そのうち必ず間違えるでしょう?それに、名前を変えたら文字列も変えなきゃならないわけで。 多分おそらく、やってられないですよコレは。

そこで、

[CallerMemberName]を使いましょう

Call Me Maybe
Call Me Maybe
posted with amazlet at 16.07.30
Universal Music LLC (2014-02-03)
売り上げランキング: 135

以下のように、OnPropertyChangedメソッドの、プロパティ名称を受け取る文字列引数を、省略可能(既定値は空文字列)にして、[System.Runtime.CompilerServices.CallerMemberName]属性を設定しておくと、引数を省略して呼び出しても、呼び出し元のプロパティ名(以下の例では"PropFoo")が自動的に渡されるのです。

using System.ComponentModel;

namespace Application {
  class ViewModel: INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    // CallerMemberNameアトリビュートを名前を受け取る文字列引数に指定する。
    public virtual void OnPropertyChanged(
      [System.Runtime.CompilerServices.CallerMemberName]
      string name = "")
    {
      if(PropertyChanged != null) {
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
      }
    }
    private int _propFoo;
    public int PropFoo {
      get { return _propFoo; }
      set {
        _propFoo = value;

        //引数を省略して呼び出すと、このプロパティ名"PropFoo"が渡される
        OnPropertyChanged();
      }
    }
  }
}

これなら、リファクタリングし放題ですねっ。

※ 上の例では、あえて冗長に属性名をフルパスで書いていますが、実際の局面では、いきなり[CallerMemberName]と書いてから(この時点ではコンパイルエラーかも)、[Ctrl]+[.]で、using System.Runtime.CompilerServicesを追加すれば良いですよ。

つまり呼び出し元のメンバ名が渡される

ここの例では、プロパティから呼び出しているのでプロパティ名になっていますが、メソッドから呼び出せば、そのメソッド名が渡されます。 CallerMemberNameの名のとおり、呼び出した側のメンバ名が渡される。

本来イベントとは何の関係もない機能ですが、気付くきっかけが「OnPropertyChangedじゃまくせー」と思って調べていた時だったのでこうなった。 デバッグログを出力するような場合にも有用でしょうね。

2. nullチェックの簡略記法

if文でオブジェクトがnullでないことを確認してから、そのメソッドを呼び出す処理は、さらにシンプルに書けます。 一つ上の例では、イベントのリスナーがいるかどうかを確認しています(以下に抜き出してます)

    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged(string name) {
      if(PropertyChanged != null) {//←ここ
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
      }
    }

長年「こういうものだ」と思っていたので特に不便さは感じていませんでしたが、最近のC#では、以下のように?を使って短く書ける。知ってしまうともう戻れない。

    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged(string name)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

対象のオブジェクト(上の例ではPropertyChanged)がnullでないならそのままメソッドを呼び出しますが、nullなら何も行わない。

最初、「え?」と思いました。そういえばnull許容型で似た記述をしますが、三項演算子の発展形(省略形?)のようにも思えます。

これは、メソッド呼び出しのための構文ではなく対象オブジェクトがnullかどうかによって、その後のメンバーの参照をするかどうかということです。 そして、以下のようにチェーンできますから、オブジェクトの階層が深い場合は、かなり有利。途中のプロパティやメソッドの戻り値がnullなら、nullとして評価され、それ以降は評価されないということですね(多分)。

  Foo?.Bar?.Baz()?.Hoge("Fuga");

メッチャ強力。過去のコードを修正したくなりますね(しないけど)。いやしかし、こりゃ楽でいい。

3. 自動実装プロパティの初期値設定

地味ではあるけど、これも由。

自動実装プロパティに特定の初期値を与えるためには、以下のように、コンストラクタで値を設定しなくてはならなかったと思っていましたが、

//従来の自動実装プロパティを持つクラス
class Foo {
  public int Bar { get; set; }
  public Foo() {
    Bar = 999;
  }
}

プロパティ宣言部分で値を設定できる(以下)ようになっていました。

//最新式(笑)の自動実装プロパティを持つクラス
class Foo {
  public int Bar { get; set; } = 999;
}

これも楽です便利です。

自動実装で楽しているにもかかわらず、コンストラクタを別途定義するとかアホちゃうかと潜在的に思っていましたが、やっと報われた(謎)。

4. 読み出し専用プロパティを自動実装

もうひとつ自動実装プロパティネタ。

GetterがパブリックでSetterはプライベートというプロパティは自動実装できないものだと思い込んでいて、常々以下のように書いていました。

class Foo {
  private int _bar = 999;
  public int Bar { get { return _bar;} }
}

でも、以下のように書けるのだとか。

class Foo {
  public int Bar { get; private set; } = 999;
}

これは、自分が知らなかっただけかな。

5. 自動実装プロパティのスニペットを挿入する

プロパティを新設するとき、全部自分でキー入力していましたが、エディタでpropと入力して、[Tab]を2回叩けば、とりあえずint型のMyPropertyという自動実装プロパティが挿入されますね。

ほかにも便利なスニペットがあるかもしれん。いろいろ探し歩いてみます。