連載:C# 4入門

第8回 ExpandoObjectを継承した動的拡張できるクラス

株式会社ピーデー 川俣 晶
2011/02/10
Page1 Page2 Page3

 前回では、動的にメンバを拡張するサンプル・コードの内容に関して、「もう1つの解決方法がある」と述べたが、今回はそれを扱おう。

 以下は前回に問題になったサンプル・コード(前回のリスト3)である。これを「もう1つの解決方法」で書き直していこう。

using System;
using System.Collections.Generic;
using System.Dynamic;

public static class Work
{
  public static void DoIt(dynamic dataStore)
  {
    dataStore.A = dataStore.B + dataStore.C;
  }
}

public class DataStore : DynamicObject
{
  private Dictionary<string, object> data
                            = new Dictionary<string, object>();

  public override bool TryGetMember(GetMemberBinder binder, out object result)
  {
    if (data.TryGetValue(binder.Name, out result)) return true;
    return false;
  }

  public override bool TrySetMember(SetMemberBinder binder, object value)
  {
    data[binder.Name] = value;
    return true;
  }
}

class Program
{
  static void Main(string[] args)
  {
    dynamic dataStore = new DataStore();
    dataStore.B = 1;
    dataStore.C = 2;

    Work.DoIt(dataStore);
    Console.WriteLine(dataStore.A);
  }
}
リスト1(第7回リスト3の再掲)

3
リスト1の実行結果

 このソースは実は使用されている機能性を一切変更せず、次のようにもっと簡潔に記述できる。

using System;
using System.Dynamic;

public static class Work
{
  public static void DoIt(dynamic dataStore)
  {
    dataStore.A = dataStore.B + dataStore.C;
  }
}

class Program
{
  static void Main(string[] args)
  {
    dynamic dataStore = new ExpandoObject();
    dataStore.B = 1;
    dataStore.C = 2;

    Work.DoIt(dataStore);
    Console.WriteLine(dataStore.A);
  }
}
リスト2

 このソースには何のトリックもない。クラス「ExpandoObject」は、動的に任意のメンバを持つことができる。AやBは誰も何も定義していないが、dynamic型を経由してそれに代入することで、動的に作成されて利用できるようになる。また、dynamic型を経由すれば簡単に読み出せる。

このコードは是か非か?

 さて問題は、このようなコードをどう解釈するかだ。果たして良いコードだろうか? 悪いコードだろうか?

 動的であることは必須の要求であるとして、ここでは論じないことにしよう(話が拡大しすぎる)。ここでは、ダイナミック・オブジェクトとExpandoObjectクラスの使い分けの問題としてとらえてみよう。

 両者には以下のようなメリットとデメリットがある。

  • ExpandoObjectは動的なメソッドやプロパティを持てないが、ダイナミック・オブジェクトなら持てる
  • ダイナミック・オブジェクトがあれば、ExpandoObjectに相当する機能を内包することも容易である。ExpandoObjectを使うことは過剰な複雑さを招く
  • ExpandoObjectを使うとコードが短くなる

 さて、ここでポイントは、「複雑さ」対「コードの短さ」という側面に絞られた感がある。コードを短くするために、機能が少ない別機能をわざわざ用意する意味が果たしてあるのだろうか。

 これに関しては、しばしば以下のような主張が行われている。

  • コードが長くなっても分かりやすく書こう

 例えば、null合体演算子は条件演算子で代用できる、条件演算子はif文で代用できるのだから、すべてif文で書く方がよいというわけである。覚えることが少なければ、生産性も上がるというわけである。読む手間も書く手間も減る。子供っぽい知識の自慢などしても他人の迷惑になるだけである。

 だが、本当にそうだろうか。

 実は最近、日経BPより「C#ショートコードプログラミング」という本を出した。趣旨は簡単で、C#で短く書くためのさまざまな方法をコードのカタログとして記載した本だ。

 このように説明すると、恐らくすぐに以下のように誤解する読者もいるだろう。

  • 空白を削って短くしても可読性が落ちるだけ
  • 機能を削って短くしても客が納得しない
  • etc、etc

 もちろんこれは誤解だ。この本で述べているのは、そういう話ではない。例えば、LINQを使うとコレクションをループしないで処理できるようになり、短くなるということだ。空白は削らないし、機能も削らない。しかし、C#のパワーをふんだんに使うとソースが短くなってしまうという話を書いているのである。もちろん、極端に読みにくい変なテクニックも使わない。ごく普通に提供されるC#の機能を普通に使うだけである。

 では、なぜ短く書くべきなのだろうか。

 いくらLINQを使うとループしないでコレクションを処理できるといっても、ループしても処理はできる。ループするなら従来からある当たり前の機能だけで同じ結果を出すことができる。ならばLINQを知らない人も読み書きできるし、いいことばかりである。代用できる上位の機能があるなら、サブセットの機能などいらないのではないか。

 このような主張は一見もっともらしく思える。私も正しいような気がした。

 しかし、実体験は違った。あるとき、手っ取り早く結論を出すために、あえて短く書いたのだが、結果として短い方が生産性が上がったのだ。

 それはなぜか。

  • ソースを読んで理解する手間が減る

 これは以下の概念と激突する。

  • 使用頻度の低い難しい機能を使うと可読性が落ちる

 しかし、実は息をするぐらいにC#に慣れ親しんだプログラマーにとって、null合体演算子も条件演算子もLINQもExpandoObjectも、しばしば見掛ける機能であり、とりたてて特殊というわけでもない。「使用頻度の低い難しい機能」には当たらないわけである。

 だから、以下のような人はこの結果に賛同しないかもしれない。

  • C#には慣れていないが、C言語が読めるのだから、それに似た構文のC#も読めるべきである

 しかし、賛同しなくてよい。なぜなら、C言語プログラマーはどのみちC#のソースを何となく読めても書けないのだ。書けない人がC#のソース・コードに貢献してくれる可能性は低い。無理に書いても、char型の配列に文字列を強引に格納しようとするおかしなソースになるのが関の山である。C言語とC#ではいくら見た目が似ていても思想がまるで違うのである。

 つまり短く書こうという話は、「コードを書く」ことで何かを実現しようというC#プログラマーだけに限った話である。ならば、C#にある機能は、C#プログラマーであれば知っていることが期待できて同然である。これらは「使用頻度の低い難しい機能」には当たらない。クラス・ライブラリの末端にある特殊な機能は「使用頻度の低い難しい機能」になるかもしれないが、言語そのものとかかわる機能はそこまで特殊とはいえない。

 「使用頻度の低い難しい機能」ではないなら、それを使って無駄なく簡潔に表現する方が生産性は上がる。当然のことである。読む手間も書く手間も減るからである。

 ちなみに、if文は読みやすく条件演算子は読みにくいというのは、ただの迷信でしかない。if文で条件判断を行わないプログラミング言語など珍しくもないし、それで生産性が低いという話も聞かない。if文以外のほかの方法で条件を判断しても何ら構わないのである。要するに慣れの問題である。

 

 INDEX
  C# 4入門
  第8回 ExpandoObjectを継承した動的拡張できるクラス
  1.このコードは是か非か?
    2.値の列挙/値のチェック/LINQでクエリする/メンバの削除
    3.メソッド・モドキの追加/まとめ
 
インデックス・ページヘ  「C# 4入門」


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 記事ランキング

本日 月間