連載:C# 2.0入門

最終回 小さな改善とコンパイラの新機能、そして3.0への展望

株式会社ピーデー 川俣 晶
2008/01/11
Page1 Page2 Page3

C# 1.xから2.0への進化とは

 以上で、本連載「C# 2.0入門」の本文は終了である。

 連載終了時、すでに次バージョンが正規リリース済みというのはあまりにタイミング的に遅れすぎた連載だったと思うが、その代わり、苦悩の果てに実際にC# 2.0を使い込んだうえで執筆ができたので、かなり実利用上の核心を突いた内容を書くことができたのではないかと思う。

 さて、結局のところC# 2.0とは何だったのだろうか。個々の機能ではなく、大きな流れとして考えてみたい。

 まずプログラム言語の大きな流れから見てみよう。ここでは大ざっぱに以下のような流れがあると仮定して話を進める。

マシン語→高級言語→構造化言語→旧世代オブジェクト指向言語→新世代オブジェクト指向言語

 マシン語とは、コンピュータが直接理解できる言語であり、人間には分かりにくいものであった。そして高級言語とは、より人間に分かりやすい書式で記述を可能にしたものである。FORTRANや初期のBASICはこれに当たる。

 ここでプログラムの構造そのものを決まった型に強制することで、より短い時間で分かりやすいプログラムを作成できる構造化言語が生まれてきた。PascalやC言語はこれに当たる。

 しかし、プログラムの複雑さが爆発することで、構造化言語でも十分な生産性を示すことができなくなってきた。そこで注目されたのがオブジェクト指向言語ということになる。最初に注目されたのは、クラスという機能をすべての基礎に置くタイプのオブジェクト指向言語である。これを、ここでは旧世代オブジェクト指向言語と呼んでいる。C++やJavaがこれに当たる。

 さて、筆者の感想を述べると、ここで「プログラミング」は1つの挫折を迎えている。マシン語→高級言語→構造化言語までの流れは、素直に生産性の向上が達成できていた。つまり、より短い時間でコードを書くことができ、より短いコードでより大きな仕事が行えたのである。

 しかし、旧世代オブジェクト指向言語は必ずしも生産性を上げてはいないのである。その状況は、以下の2つの問題が複合して発生したためと筆者は考える。

  • すべての機能をたった1つのクラスという機能を通じて実現するのは硬直的すぎ、無理がある
  • 「正しいオブジェクト指向」を習得すればすべてがうまくいくという幻想が幅広く信じられた

 つまり、現実的にクラス「だけ」を用いてプログラムを構築する方法論がうまく機能しないにもかかわらず、うまくいかない理由を錯覚させる風説が信じられていたのである。その結果、筆者もすっかり旧世代オブジェクト指向言語の奴隷として拘束されてしまっていた。

 このような状況は、20世紀から21世紀に時代が移り変わるころには限界状況を呈していたと感じる。そして、それを打破したのが新世代オブジェクト指向言語といえる。ここでいう新世代オブジェクト指向言語とは、クラスという機能を持たない、あるいはクラスだけにすべての役割を押し付けない言語を意味する。JavaScriptやC#がそれに当たる。

 このような流れの中で、C#は「旧世代オブジェクト指向言語の奴隷」からの解放者として出現した感がある。事実として、筆者はC# 1.0の登場により大きな解放感を感じることができた。Javaで行き詰まっていたプログラムが、C#にソースをコンバートすることで生き返り、開発を進めることが可能になったのである。

 さて、ここまでは前振りでしかない。

 解放者として出現したC# 1.xは、確かに旧世代オブジェクト指向言語から筆者を解放してくれた。だが、C# 1.xがやってくれたのは、ただ単に筆者の手につながれた鎖を外しただけである。「さあ、君はもう奴隷ではないよ」と告げられただけである。

 ここで問題となるのは、解放され、自由になった筆者はどこへ行けばよいのか……である。JavaではうまくできなかったプログラムをC# 1.xでまとめ上げるまでは気持ち良くプログラミングができたが、そこまでである。それ以後、筆者はどこにも行き先がないことに戸惑ってしまったわけである。

 C# 2.0の存在意義とは、まさにここにある。

 実は、C# 1.xが解放者であるとすれば、C# 2.0とは進むべき道筋を示す予言者であったといえる。予言者とは、あまりにも突飛で信じがたい未来を語る者であり、頭のおかしい人物と見えることもある。事実、筆者もC# 2.0はおかしいのではないかと思ったことがある。

 しかし、そうではなかった。匿名メソッドを使い、整数や文字列のようにコードの断片を気軽に代入して受け渡すプログラミングは、常軌を逸した奇行に見えたが、それは有効であったのだ。そのようなテクニックは、より短いコードでより強力かつ柔軟なプログラムを実現してくれる。つまり、マシン語→高級言語→構造化言語といった劇的な変化により実現された生産性の向上が、再び得られたのである。

 つまり、オブジェクト指向言語の台頭後、本当の意味での劇的な一歩はC# 2.0によって踏み出されたと筆者は感じるわけである。この一歩を踏み出すか否かは非常に重要な選択となる。例えば、アセンブラに熟練したプログラマーがC言語に乗り換えるか否か、FORTRANに熟練したプログラマーがPascalに乗り換えるか否か、といった決断に匹敵する大きな意味を持つのではないかと思う。

 現時点でそれを踏み出すか否かは個々の技術者の判断であり、踏み出すことを強制する意味はないだろう。しかし、ふと気付くと、いつの間にかそれが常識になっていた……という状況は十分にあり得ると思う。

C# 2.0から3.0への展望

 C# 1.xが解放者、C# 2.0が予言者だったとすると、C# 3.0は何に当たるのだろうか。

 C# 3.0は開拓者だというのが、現在の筆者の考えである。つまり、C# 2.0が指し示した「可能性」としての未来は、C# 3.0では「現実」に置き換えられる。

 例えば、コードの断片が整数や文字列のように気軽に受け渡されるという可能性をC# 2.0は匿名メソッドという形で語ったが、これはC# 3.0の「ラムダ式」によって当たり前の風景に変貌させられる。

 以下、具体的に説明しよう。

 匿名メソッドは意識的にdelegateというキーワードをたたくことで書き始め、メソッドの宣言のように引数リストに型を添えて書かねばならなかった。つまり、「匿名メソッドを書くぞ」と心に決め、意識的に書き始める必要があった。慣れればそうでもないが、整数や文字列の宣言などに比べれば大幅に手間のかかる書式だったといえる。

 例えば、次のリスト3のSortメソッドとForEachメソッドの引数は匿名メソッドである。

using System;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    List<int> list = new List<int>();
    list.Add(1);
    list.Add(3);
    list.Add(2);

    list.Sort(delegate(int x, int y) // 匿名メソッド
    {
      return x - y;
    });

    list.ForEach(delegate(int i) // 匿名メソッド
    {
      Console.WriteLine(i);
    });
    // 出力:
    // 1
    // 2
    // 3
  }
}
リスト3 匿名メソッドの使用例

 この2つのメソッドの引数をC# 3.0のラムダ式で書き直すと以下のようになる。

list.Sort( (x, y) => x - y );

list.ForEach( i => Console.WriteLine(i) );
リスト4 リスト3のSortメソッドとForEachメソッドをラムダ式に置き換えた例

 もはやdelegateというキーワードは必要ない。引数の型を指定する必要もない。なぜなら、型はSortメソッドやForEachメソッドの定義から厳格に推定可能だからである。さらにreturnキーワードも必要がない。ラムダ式は「式」だからである。式の値そのものが使用される。また、値を返さない式を書いてもよい。ForEachメソッドの引数に書いたラムダ式は、値を返さない式である。

 そして、式なので文の最後に付けるセミコロンもいらない。実行文をくくるための「{}」もいらない。それらを除去する代償として記述するのはわずかに「=>」という演算子だけである。

 ちなみに、式では不十分である場合は以下のようにブロック形式で書いてもよい。

list.ForEach( i => { Console.WriteLine(i); });

 しかし、そのために増えるのは実質的に「{」「}」「;」の記号3文字にすぎない。

 匿名メソッドに比べれば、まさに圧倒的な短さだ。これならば、整数や文字列に近い気軽さで、いくらでもコードの断片を記述することができる。もはや、こう書いたらよいのではないか、あるいは書くべきであるというレベルではない。そう書くのが当たり前という前提で、気軽に書けるように構文が拡張されている。

 このようなメリットは、コレクションに追加されたメソッドによって発揮される。

 第4回にも少し紹介したが、データを固まりとして扱う行列演算などの機能を持つプログラミング言語は過去にも存在していた。それらの言語の使い勝手が悪かった理由の1つは、固まりに対する演算の種類が乏しく、単調であったことではないかと思う。

 だが、コードの断片を気軽に受け渡せるようになれば、固まりの処理の自由度が大幅にアップする。例えば、リスト5は文字列のコレクションから“最大値”を取り出すサンプルだが、文字列は数値ではないので最大、最小という区別は付けにくい。数値として解析した結果の大きさなのか、文字列の長さなのか、いくらでも解釈があり得る。それ故に、一律のコレクションの最大値を得る演算は有効性が限られてくる。

 しかし、コードの断片を気軽に渡せれば話は変わる。以下のとおり、コレクションの最大値を得る(厳密には値のシーケンスの最大値を得る)Maxメソッドは、

x => x.Length

というラムダ式によってx.Lengthつまり文字列の長さを基準とするという指定を入れることで、プログラムの思いどおりに基準を示すことができている。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
  static void Main(string[] args)
  {
    var list = new List<string>();
    list.Add("1");
    list.Add("12");
    list.Add("123");
    Console.WriteLine(list.Max( x => x.Length )); // 出力:3
  }
}
リスト5 コレクションの最大値を得る(C# 3.0)

 このMaxメソッドは、System.Linq名前空間にあるので、.NET Framework 3.5の新機能であるLINQ(Language Integrated Query)の一種といえる。もっとも、LINQそのものの構文を使えば、もっと直接的にデータの固まりに対する演算処理を記述することができる。

 例えば、リスト6はLINQを用いて、文字列のコレクションから、それぞれの文字列を大文字に変換したコレクションを作る例である。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
  static void Main(string[] args)
  {
    var list = new List<string>();
    list.Add("Transformer");
    list.Add("Super");
    list.Add("Linq");

    var queryResult = from n in list select n.ToUpper(); // LINQ構文

    foreach( string s in queryResult ) Console.WriteLine(s);
    // 出力:
    // TRANSFORMER
    // SUPER
    // LINQ
  }
}
リスト6 LINQで文字列のコレクションから大文字化したコレクションを作る(C# 3.0)

 このサンプルソースには匿名メソッドもラムダ式も含まれていないが油断してはいけない。実は、

from n in list select n.ToUpper()

という式で生成されたオブジェクト(リスト6では変数queryResultに格納される)は、コレクションではない。これはLINQのクエリを解釈しながら列挙を行う、れっきとした列挙オブジェクト(IEnumerableインターフェイスを実装したオブジェクト)なのである。つまり、結果は「真の結果」ではなく、結果を得るための一種のコードの断片だということである。これもコードの断片を気軽に受け渡して使う……という大きな動きの一部といえる。

 本連載でC# 3.0について語るのは本論ではないのでここで終わるが、このようなサンプル・コードを見ていると、まさにC# 2.0で予言された未来が、現実に開拓すべきフィールドとしてやってきたという感がある。ぜひ皆さんも、C# 2.0の世界に足を踏み入れ、そしてそれを踏み越えてC# 3.0の世界を目指していただきたいと思う。

 「Pascalで書こうよ!」と、マシン語や非構造化BASICのプログラマーたちに語り掛けた昔を思い出しつつ筆を置くことにする。End of Article

 

 INDEX
  C# 2.0入門
  最終回 小さな改善とコンパイラの新機能、そして3.0への展望
    1.固定サイズ・バッファ/volatileキーワード/#pragma warning
    2.C#コンパイラの新機能
  3.C# 1.xから2.0への進化とは/C# 2.0から3.0への展望
 
インデックス・ページヘ  「C# 2.0入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間