Lambda表达式的使用范例
到现在为止的例子大多数,完全都是从文法上到功能上的说明,多数情况下不必使用Lambda表达式也能够写出来。最后,我还是来展示几个有一定实用性的Lambda表达式的实例。
这里是一个,输入整形的数组,以其值为除数,执行除法运算后输出结果的程序。因为要执行除法运算,所以输入的值可能存在不能接受的值(0)的情况(就是说,这个示例代码可以在输入值不正确的情况下也能正常工作)。
那么,这个程序的输入如果是0的情况下,就提示信息然后必须退出。这个意图如果用Lambda表达式该怎么写呢?
这个问题的要点有两个。
l 在执行计算的循环中检查0值的话,计算开始后会发生中断。就是说,可能会同时计算值与错误信息两者都输出。
l 如果不输出计算值只输出错误信息的话,最好在计算的循环之前在设置一个检查0值的循环。但是,这样写的话就用了两个循环太冗长了。
总之,两个方法都有缺点。换做笔者,如果有“计算开始前应该先检查完毕”的要求,去除繁冗更重要,所以更愿意选择在循环内判定。2
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 int[] a = { 1, 2, 0 };
8
9 foreach (int n in a)
10 {
11 if ( n == 0 )
12 {
13 Console.WriteLine("数组里面含有0。");
14 return;
15 }
16 Console.WriteLine(100 / n);
17 }
18 // 输出:
19 // 100
20 // 50
21 // 数组里面含有0。
22 }
23}
24
25
然而,如果使用Lambda表达式,就会有一种完全不同的答案。要判断所有数组的元素是否都满足一定的条件,只需用一个调用一个方法的Lambda表达式,事情就有着落了。在不损失可读性的范围内,把Lambda表达式写进if语句的条件中就能轻松的实现了。
实际写出来的例子如下。Any方法是,集合的元素中只要含有一个满足参数的Lambda表达式的元素就返回true。这个用到了.NET Framework的扩展方法,为了能够使用,要在代码前面加上“using System.Linq;”。
2 using System.Linq; // 为了使用Any方法
3
4 class Program
5 {
6 static void Main(string[] args)
7 {
8 int[] a = { 1, 2, 0 };
9
10 if (a.Any(x => x == 0))
11 {
12 Console.WriteLine("数组中含有0");
13 return;
14 }
15 foreach (int n in a) Console.WriteLine(100 / n);
16 }
17 // 输出:数组中含有0。
18}
19
如果是不能使用.Net Framework 3.5的场合,List19的if语句就要写成下面的形式(稍稍有些冗长),这样在.NET Framework 2.0下也同样能够使用。
这个例子,用Lambda表达式对集合数据的处理,感觉就像对单个值进行判断的“if ( n == 0 )”一样,对集合使用“if (a.Any(x => x == 0))”就能够处理。
总之吧,使用循环的方法对集合元素进行判断处理的方法,用仅仅一句 if 就能给替换了。如果经常使用这种技术,代码的结构也会更明快简洁。【短评】“=>”是不等号?
BASIC系的语言中,不等号的写法,“=”与“>”的顺序没有明确的规定。这样的语言中,“>=”“=>”是等价的。正因为如此,熟悉了这样的语言的话“=>运算符”就很容易会被误认为是不等号。
例如如果有下面的Lambda表达式,如果是有上述语言的使用经验的人看来,很容易就认为是不等号了。
|
但是,使用Visual Basic的IDE的程序员,几乎就不会看见作为不等号的“=>”。因为在IDE中输入“=>”时,当场就会被自动替换为“>=”(Visual Basic 6.0与Visual Studio 2008会要求确认)
所以综合起来考虑,如果是使用会把“=>”与不等号搞混的编程语言,那最好就要养成良好的编程习惯。
Lambda表达式的各种变体
Lambda表达式有各种变体。特别是在参数只有一个的情况下,因为在语法上可以省略参数两边的括弧,全面把握起来会比较困难。所以,下面的代码给出几种主流的变体的写法。
2
3 delegate int NoArg();
4 delegate int OneArg( int onlyOne);
5 delegate int TowArgs( int first, int second);
6 delegate void NoRetVal();
7
8 class Program
9 {
10 static void Main(string[] args)
11 {
12 NoArg sample1 = () => 0;
13 OneArg sample2 = (x) => x * 2;
14 OneArg sample3 = (int x) => x * 2;
15 OneArg sample4 = x => x * 2;
16 TowArgs sample5 = (x, y) => x * y;
17 TowArgs sample6 = (int x, int y) => x * y;
18 OneArg sample7 = (x) => { return x * 2; };
19 OneArg sample8 = (int x) => { return x * 2; };
20 NoRetVal sample9 = () => Console.WriteLine("Hello!");
21 }
22}
23
sample1 = () => 0
在写没有参数的Lambda表达式的时候,要写上没有参数的空的括号。
sample2 = (x) => x * 2
有参数的情况下,用括号把参数表括上是最基本的写法。
sample3 = (int x) => x * 2
当然,如果能够明确指定参数的类型更好。
sample4 = x => x * 2
仅当参数只有一个的时候,参数两边的括弧可以省略。
sample5 = (x, y) => x * y
参数有多个的时候,用逗号分隔
sample6 = (int x, int y) => x * y
参数有多个的时候,也可以指定类型
sample7 = (x) => { return x * 2; }
Lambda语句的例子
sample8 = (int x) => { return x * 2; }
Lambda语中也可以指定类型
sample9 = () => Console.WriteLine("Hello!")
没有返回值的情况,写成调用void型返回值的方法的形式。
下期预告
介绍到这里,Lambda表达式的大部分内容已经介绍过了。仅仅运用本章的知识,就能够使代码发生很大的质的变化了。
然而,如果说要完全理解了Lambda表达式的话,仅仅这样的话还为时尚早。
实际上,推断被省略了的类型声明的“类型推断”的麻烦事还在后头呢。下一章,就来说说类型推断。同时,也将会讨论一下使用Lambda表达式的代理类型在类库中的使用方法。例如,本章中的例子里就用到了Action类型和Func类型。
以下是日文原文
ラムダ式の使用例
ここまでのサンプル・コードの大多数は、あくまで構文や機能を説明するもので、必ずしもラムダ式を使用しなくても書けるものが多かった。最後に、ある程度実用性のあるラムダ式の使用例をお見せしよう。
ここでは、整数の配列の各要素を入力とし、その値で割り算を実行して結果を出力するプログラムを作成するとしよう。ここで割り算を使ったのは、入力として受け付けられない値(0)が存在するからである(つまり、このサンプル・コードは不正な入力値が存在する事例一般に応用できる可能性がある)。
さて、このプログラムは入力が0の場合は、その旨を出力して終了しなければならない。この意図をラムダ式抜きで記述するとしたら、どのように書くだろうか?
この問い掛けのポイントは2つある。
- 計算を行うループ内で値の0チェックを行うと、計算が始まった後で中断が発生する。つまり、計算値とエラー・メッセージの双方が出力されてしまう可能性がある
- 計算値を出さずにエラー・メッセージを出すには、計算ループよりも手前に0チェックのためのループを設ける必要がある。しかし、それを書くとループが2つになって冗長すぎる
つまり、どちらの方法もデメリットを抱えている。筆者であれば、「計算開始前にチェックを完了すべき」という要求が存在しなければ、冗長さを除去する方が重要だと考え、ループ内での判定の方を選ぶ。
|
|
リスト18 ループ内で判定する場合 |
しかし、ラムダ式を使用してよければ、まったく別個の答えが出せる。配列の要素がすべて特定の条件を満たしているかを調べるだけなら、メソッド呼び出し1つとシンプルなラムダ式1つでケリがつくからだ。これは、読みやすさを損なわない範囲内で、if文の条件として楽に記述できる文字数となる。
実際に記述した例を以下に示す。Anyメソッドは、コレクションの要素の中に1つでも引数のラムダ式を満たすものが含まれていればtrueになる。これは.NET Framework 3.5で拡張されたメソッドであり、使用するためにはコードの先頭に「using System.Linq;」が必要である。
|
|
リスト19 ラムダ式を用いて計算開始前に0チェックを行う |
もし.NET Framework 3.5が使用できない場合は、リスト19のif文を以下のように記述することで(やや冗長になるが).NET Framework 2.0でも同様のチェックを実現できる。
|
この例は、データの固まり(コレクション)に対する処理の指示を短いラムダ式で記述することにより、1つの値に対するチェックである「if ( n == 0 )」と同じような感覚でコレクションに対するチェック「if (a.Any(x => x == 0))」を記述できることを示している。
つまり、これまでコレクションに対するループとして記述されてきたコレクション要素の判定処理が、たった1つのif文に置換できることを示している。このようなテクニックを常用すると、明らかにソース・コードの雰囲気が変わり、簡潔になる。
【コラム】“=>”は不等号? BASIC系の言語などでは、不等号を記述する際に「=」と「>」の順番が規定されていないことがある。そのような言語では、「>=」と「=>」の意味は等価である。そのため、このような言語に親しんでいると「=>演算子」が不等号を記述しているように見えることがあるかもしれない。 例えば以下のようなラムダ式は実際にあり得るし、これは上記のような言語の経験者にはまさに不等号そのもの見えるだろう。
ただし、Visual BasicのIDEを使うプログラマーであれば、不等号としての「=>」はめったに見ていないはずである。なぜかといえば、IDEで入力した際に、「=>」は即座に「>=」に置き換えられてしまうからである(Visual Basic 6.0とVisual Studio 2008で確認)。 それらを考え合わせると、「=>」を不等号として扱うことのできるプログラミング言語でコードを書く場合でも、そのような表記は使わないように習慣づけるとよいだろう。 |
ラムダ式のさまざまなバリエーション
ラムダ式には、さまざまな書き方のバリエーションがある。特に引数が1つの場合のみ、引数を囲うカッコを省略できるといった特殊な構文も存在するので、全ぼうを把握しにくい。そこで、主立った書き方のバリエーションを以下にまとめておく。
|
|
リスト20 ラムダ式のさまざまなバリエーション |
sample1 = () => 0
引数が存在しないラムダ式を記述する場合は、引数のない空のカッコで書き始める。
sample2 = (x) => x * 2
引数が存在する場合はそれを(x)のようにカッコで囲うのが基本である。
sample3 = (int x) => x * 2
もちろん、引数に型指定を補ってもよい。補わねばならないケースは本文で紹介したとおり存在する。
sample4 = x => x * 2
引数が1つの場合に限っては、引数を囲うカッコを省略してもよい。
sample5 = (x, y) => x * y
引数が複数ある場合はカンマで区切って列挙する。
sample6 = (int x, int y) => x * y
引数が複数ある場合でも、型指定は可能。
sample7 = (x) => { return x * 2; }
ステートメント型のラムダとして記述した例。
sample8 = (int x) => { return x * 2; }
ステートメント型のラムダでも引数の型は指定できる。
sample9 = () => Console.WriteLine("Hello!")
戻り値がない場合は、式としてvoid型を返すメソッドの呼び出しを記述できる。
次回予告
ここまでの説明で、ラムダ式のかなりの部分は説明できたと思う。今回の知識だけで、ソース・コードの質を大きく変えていくことができるだろう。
しかし、これでラムダ式を完全に理解したと思うのは早い。
実は、省略された型を調べる「型推論」という厄介な問題が残っているためだ。次回はこの型推論について説明したい。それに加えて、ラムダ式で使用するデリゲート型をクラス・ライブラリから利用する方法についても解説する予定である。例えば、今回のサンプル・コードでも使用したAction型やFunc型などである。