連載:C# 2.0入門

第1回 総論:C# 2.0らしいプログラミングとは

株式会社ピーデー 川俣 晶
2007/06/01
Page1 Page2 Page3 Page4

コードの遅延実行という例

 もう1つ簡単な例を見てみよう。

 同じ同人ゲームのコードの一部である(ただし、例として分かりやすいように実際のコードから改変している部分がある)。

QuickTalk q = new QuickTalk();
q.AddTalker("y", t.闇之ハヤト);

……中略……

int 語れるレベルカウント = 0;
SimpleMenuAction 語り = null;

if (General.Is江口高枝エンディング条件)
{
  語れるレベルカウント++;
  if (!State.IsFlag("闇之ハヤトから江口高枝の評価を聞いた"))
  {
    語り = delegate() // 匿名メソッド
    {
      q.Play(@"
y そうそう。
y 江口高枝さんから、あなたの噂を聞きました。
……中略……
");
      State.SetFlag("闇之ハヤトから江口高枝の評価を聞いた");
      return true;
    };
  }
}

……中略……

// %表示は20%から始まり4つの条件を満たすと100%になる
// つまり、闇之ハヤトは初期状態の主人公を20%は認めている
int 語れるレベル百分率 = (語れるレベルカウント + 1) * 20;

t.闇之ハヤト.Say("現在、『真相のさわり』を語って上げられる水準は{0}%といったところですね。", 語れるレベル百分率);

// 語りがあれば語らせる
if (語り != null)
{
  語り();
  ……中略……
}
リスト3 コードの遅延実行(C# 2.0)

 このメソッドでは、いくつかのイレギュラーな条件をカウントし、それによって動作を分けるとともに、それぞれの条件によってリアクションとしての「語り」が発生する場合がある。

 この処理で問題になるのは、成立した条件の数を知るためにはすべてのイレギュラーな条件の判定を行う必要があるため、条件を判定した瞬間にはまだリアクションができない点にある。

 そこで、条件を判定した瞬間に、未来に行うべきリアクションの内容をデリゲート型の変数に匿名メソッドとして代入して保管しておき、後で語るべきときが来たら語らせるという構造を取った。

 このような遅延実行はC# 1.xプログラミング……というよりC++からJavaを経由して続くPC上のOOPプログラミングではあまり見られないものかもしれない。しかし、JavaScriptプログラミングでは、SetTimeOut関数を用いた遅延実行はよくあるテクニックである。もちろん、上記のコードはSetTimeOut関数による遅延実行とは機能性がまったく違うのだが、「まだ実行できないコードを事前に記述しておく」というアイデアはSetTimeOut関数からの連想である。

 恐らく、JavaScript(やそれに類するほかの言語)になじんだプログラマーは、このようなコードをC# 2.0でも当たり前のように平然と書くだろう。しかし、C# 1.xプログラマーは、このような書き方を見慣れていない可能性があり、驚くかもしれない。

インターフェイスとの比較

 ここまで見てきた2つの事例は、匿名メソッドを使わずとも、継承、インターフェイス、ポリモーフィズムなどを使ってきれいに実現できる……という反論があり得るだろう。

 これに答えるために、インターフェイスと匿名メソッドを比較するサンプル・コードを紹介しよう。

 内容は簡単で、名前と年齢のペアを含むオブジェクトの配列を年齢順にソートを行うというだけのものである。ただし、コマンドラインに引数を与えられた場合は逆順にソートするとしよう。

 リスト4は、匿名メソッドと、これまたC# 2.0の新機能であるジェネリックを使った例である。リスト5は、インターフェイスを使い、できるだけ同等になるようVisual Studio .NET 2003(C# 1.x)で作成したものである。

using System;

class Person
{
  public readonly string Name;
  public readonly int Age;
  public Person(string name, int age)
  {
    Name = name;
    Age = age;
  }
}

class Program
{
  static void Main(string[] args)
  {
    Person[] persons = {
      new Person("加藤三郎", 36),
      new Person("浪速十三", 13),
      new Person("山本五十六", 56),
    };

    // このSortメソッドはジェネリック・メソッドであり、
    // 任意の型の配列をソートできる
    Array.Sort(persons, delegate(Person x, Person y)
      {
        return (x.Age - y.Age) * (args.Length > 0 ? -1 : 1);
      });

    foreach (Person person in persons)
    {
      Console.WriteLine("{0},{1}", person.Name, person.Age);
    }
  }
}
リスト4 匿名メソッドとジェネリックを使ったソート(C# 2.0)

using System;
using System.Collections;

class Person
{
  public readonly string Name;
  public readonly int Age;
  public Person(string name, int age)
  {
    Name = name;
    Age = age;
  }
}

class PersonComparer: IComparer
{
  private int sign;

  int IComparer.Compare (object x, object y )
  {
    // Person型へのキャストが必要
    return (((Person)x).Age - ((Person)y).Age) * sign;
  }

  public PersonComparer(bool isReverse )
  {
    sign = isReverse ? -1 : 1;
  }
}

class ClassMain
{
  [STAThread]
  static void Main(string[] args)
  {
    Person[] persons = {
      new Person("加藤三郎", 36),
      new Person("浪速十三", 13),
      new Person("山本五十六", 56),
    };

    Array.Sort(persons, new PersonComparer(args.Length > 0));

    foreach (Person person in persons)
    {
      Console.WriteLine("{0},{1}", person.Name, person.Age);
    }
  }
}
リスト5 C# 1.xによるソート

 リスト4では、ソート条件の処理を、匿名メソッド1つで処理している。その内容は、実質的にreturn文に書かれた式1つである。この式には、コマンドライン引数の有無を調べる部分が含まれるが、もちろん匿名メソッドは上位のメソッドのスコープに含まれるので、上位メソッドの引数(この場合には変数args)にアクセスできる。

 これに対して、リスト5は比較にならない複雑さを持っている。クラスPersonComparerを宣言し、IComparerインターフェイスを実装するだけでも行数の増加だが、それだけではない。実は、Compareメソッド内で正順か逆順かを判断するために、コンストラクタで順序の指定を受け取って、値をインスタンス内に保存しておかねばならない。そのためのコードにより、PersonComparerクラスはより大きく膨らんでしまっている。

 そのうえ、ジェネリックもないので、コンパイル時には安全性を確認できないキャストも入ってしまっている。

 以上で最初の問い掛けに答えられるだろう。

 まず、インターフェイスを使っても匿名メソッドと同じことが可能なのは間違いないが、そのために費やされる文字数が大きく違う。しかも、本質とは無関係の記述が多い。ソートのための比較機能は、たった1つのメソッドさえ渡せば済む話であるのに、わざわざ本質的に必要のないクラスを宣言したうえで、インターフェイスを実装するという余計な手間をかけている。

 これは単に匿名メソッドを使うと文字数が少ないという話ではない。本質と関係ない記述を極限まで切り落としたシンプルな記述を可能にした、ということである。それ故に、このケースでは、インターフェイスを使うよりも、匿名メソッドを使う方が可読性や保守性の高いソース・コードが得られていると見るべきだろう。


 INDEX
  C# 2.0入門
  第1回 総論:C# 2.0らしいプログラミングとは
    1.意外性あり? この連載で解説すること
    2.C# 2.0らしいソース・コードとは?
  3.インターフェイスとの比較
    4.後退するクラスの立場、クラスの持つ問題点
 
インデックス・ページヘ  「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 記事ランキング

本日 月間