July 2010

Volume 25 Number 07

C# 4.0 - .NET Framework 4 における C# の新機能

Chris Burrows | July 2010

C# プログラミング言語は、2002 年に最初にリリースされて以来、プログラマがわかりやすく、保守しやすいコードを記述できるように改良されてきました。ジェネリック型、Null 許容型、ラムダ式、反復子メソッド、部分クラス、その他数多くの便利な言語構成要素などの機能が追加されることによって、拡張が行われてきました。また、サポートに対応する Microsoft .NET Framework ライブラリを提供することによって、変更が組み込まれてきました。

ユーザビリティを高めるというこの考え方は、C# 4.0 でも続いています。追加機能により、ジェネリック型、レガシ システムの相互運用、動的オブジェクト モデルの操作などの一般的作業がずっと簡単になります。今回の記事の目的は、これらの新機能の概要を説明することです。まずはジェネリック型の分散から始め、レガシ システムの相互運用機能や、dynamic の相互運用機能について説明していきます。

共変性と反変性

共変性と反変性は、例を挙げるとわかりやすくなります。その最もわかりやすい例はフレームワークにあります。System.Collections.Generic では、IEnumerable<T> と IEnumerator<T> が、T のシーケンスであるオブジェクトと、そのシーケンスを反復処理する列挙子 (または反復子) をそれぞれ表わします。これらのインターフェイスは foreach ループ構造の実装をサポートするため、長い間複雑な処理を数多く行ってきました。C# 3.0 でのこれらのインターフェイスはシーケンスを表わす .NET インターフェイスであり、LINQ と LINQ to Objects において中心的な役割を果たすため、さらに目立つものになりました。

したがって、従業員を表す Employee 型と、マネージャーを表す Employee 型から派生した Manager 型 (結局、マネージャーも従業員の 1 人というわけです) というクラス階層があるとすると、次のコードによって何が行われると想定するでしょう。

IEnumerable<Manager> ms = GetManagers();
IEnumerable<Employee> es = ms;

マネージャーのシーケンスを従業員のシーケンスとして扱うことは当然であるように思えます。ですが C# 3.0 では、この代入は失敗し、コンパイラによってこのような変換は存在しないことが通知されます。つまり、コンパイラには IEnumerable<T> のセマンティクスが何かがわからないというわけです。このコードにはどのインターフェイスも使用できます。そこで、任意のインターフェイス IFoo<T> を使用すると、IFoo<Manager> が事実上 IFoo<Employee> と置換できるのはなぜでしょう。

C# 4.0 では、IEnumerable<T> がその他いくつかのインターフェイスと共に変更されたため、この代入が機能するようになります。変更は、C# で型パラメーターの共変性を新しくサポートすることで可能になりました。

IEnumerable<T> は、任意の IFoo<T> よりも特殊です。なぜなら、一見しただけでははっきりとわかりませんが、T 型パラメーターを使用するメンバー (IEnumerable<T> の GetEnumerator と、IEnumerator<T> の Current プロパティ) は、実際には戻り値の位置のみに T を使用するからです。したがって、シーケンス内から Manager を取得することはあっても、シーケンス内に Manager を格納することはありません。

これに対して、List<T> について考えてみましょう。List<Manager> を List<Employee> に置換できるようにすると悲惨なことになります。その理由は次のとおりです。

List<Manager> ms = GetManagers();
List<Employee> es = ms; // Suppose this were possible
es.Add(new EmployeeWhoIsNotAManager()); // Uh oh

このとおり、当該の一覧が List<Employee> であると考えれば、あらゆる従業員を挿入できますが、実際には List<Manager> なので、Manager 以外を挿入するとエラーにならなければなりません。これを許可してしまうと、タイプセーフ性が失われます。List<T> は、T では共変型になれません。

C# 4.0 の新しい言語機能では、新しい IEnumerable<T> などの型を定義できるようになります。これらの型は、これらの型の型パラメーターが互いになんらかの関係を持ったときに、これらの型間での変換を可能にします。IEnumerable<T> を記述する .NET Framework の開発者が使用するコードがどのようなものかを次に示します (もちろん簡略化しています)。

public interface IEnumerable<out T> { /* ... */ }

T 型パラメーターの定義を変更している out キーワードに注目してください。コンパイラはこのキーワードを認識すると、T を共変であるとマークし、インターフェイスの定義内で T の使用がすべて基準に達しているかどうか確認します (つまり、出力パラメーターでのみ使用されていることを確認します。out というキーワードが使用されるのはこのためです)。

なぜこれが共変性と呼ばれるのかは、矢印を使うと一番わかりやすいと思います。具体的に説明するために、Manager 型と Employee 型を使いましょう。これらのクラス間には継承関係があるため、次のように Manager から Employee への暗黙の参照変換が行われます。

Manager → Employee

そして今度は、IEnumerable<out T> 内の注釈 T のため、次のように、IEnumerable<Manager> から IEnumerable<Employee> への暗黙の参照変換も行われます。これがこの注釈の役割です。

IEnumerable<Manager> → IEnumerable<Employee>

2 つの例の矢印はそれぞれ同じ方向を指しているので、これを共変性と呼びます。ここでは、まず、Manager と Employee という 2 つの型から開始しました。そこから、IEnumerable<Manager> と IEnumerable<Employee> という新しい型を作成しました。新しい型は、既存の型と同じように変換します。

これが逆方向に行われると、反変性になります。T 型パラメーターが入力としてのみ使用されれば逆方向になると予想されるかもしれませんが、まさにそのとおりです。たとえば、System 名前空間には、IComparable<T> というインターフェイスがあります。IComparable<T> には、次のように、CompareTo というメソッドが 1 つあります。

public interface IComparable<in T> { 
  bool CompareTo(T other); 
}

IComparable<Employee> があるとすると、それを IComparable<Manager> であるかのように処理できる必要があります。これは、唯一可能なのがインターフェイスに Employee を格納することだからです。マネージャーは従業員の 1 人なので、Manager を格納することは問題ないはずですが、実際にそのとおりです。この場合、in キーワードは T を変更し、このシナリオは次のように適切に機能します。

IComparable<Employee> ec = GetEmployeeComparer();
IComparable<Manager> mc = ec;

これが反変性と呼ばれるゆえんは、今回は次のように矢印が逆になるからです。

Manager → Employee
IComparable<Manager> ← IComparable<Employee>

したがって、この言語機能を要約するのはかなり簡単です。型パラメーターを定義するときはいつでも、in か out のキーワードを追加でき、こうすることによって、新しい変換を自由に実行できます。ただし、いくつか制限があります。

まず、これはジェネリック インターフェイスとジェネリック デリゲートでのみ機能します。この方法だと、クラスまたは構造体でジェネリック型パラメーターを宣言できません。これを合理化する簡単な方法の 1 つは、デリゲートを、メソッドが 1 つだけあるインターフェイスとほぼ同じにすることです。しかし、どのような場合でも、クラスではフィールドが原因でこの処理を実行できないことがほとんどです。ジェネリック クラスにあるフィールドは、書き込み先か読み取り元かどうかに基づいて、入出力の両方として考えることができます。こうしたフィールドに型パラメーターが含まれると、その型パラメーターは共変にも反変にもなれません。

2 つ目に、共変の型パラメーターまたは反変の型パラメーターを持つインターフェイスかデリゲートがある場合は、常に、型引数が (インターフェイスの定義ではなく) インターフェイスでの使用において参照型である場合のみ、新しい変換を実行できます。たとえば、int は値型なので、IEnumerable<int> は IEnumerable<object> に変換されるように思われても、次のように変換は行われません。

IEnumerator <int> image: right arrow with slash  IEnumerator <object>

この動作は、変換が型表現を保持する必要があることが原因です。たとえ int から object に変換できたとしても、変換後の Current プロパティを呼び出すのは不可能です。スタックでの int 値型の表現は、スタックでのオブジェクト参照の表現とは異なります。ですが、スタックでの参照型の表現はすべて同じです。このため、参照型である型引数のみが新しい変換を生成します。

おそらく、ほとんどの C# の開発者は、この新しい言語機能を喜んで使用するでしょう。これにより、フレームワークの型をより多く変換でき、.NET Framework のいくつかの型 (特に IEnumerable<T>、IComparable<T>、Func<T>、および Action<T>) を使用するときに発生するコンパイラ エラーが少なくなります。また、実際に、ジェネリック インターフェイスとジェネリック デリゲートを使ってライブラリを設計する開発者は、ユーザーが作業しやすくなるように、必要に応じて新しい in 型パラメーターと out 型パラメーターを使用できます。

ちなみに、この機能はランタイムでサポートされる必要がありますが、実は、ランタイムでは常にサポートされてきました。ですが、この機能を活用する言語がないため、いくつかのリリースではサポートが休止されています。また、以前のバージョンの C# では、反変性の変換のいくつかを制限付きで実行できました。具体的に言うと、互換性のある戻り値の型を持つメソッドからデリゲートを作成できました。さらに、配列型は常に共変でした。こうした既存の機能は、いくつかの型パラメーターにおいて独自の共変の型と反変の型を定義できる、C# 4.0 の新機能とは異なるものです。

動的ディスパッチ

おそらく、C# 4.0 の相互運用機能の中で最も大きく変更されたのは、何から開始するか、という点です。

C# では、動的な遅延バインディングがサポートされるようになりました。C# では常に厳密に型指定されていましたが、バージョン 4.0 でも引き続き厳密に型指定されます。マイクロソフトはこうすることで、C# が使いやすく、高速で、.NET のプログラマが C# を使用するすべての作業に適するようになると考えていますが、.NET に基づいていないシステムと通信しなくてはならない場合もあります。

従来、これには少なくとも 2 つのアプローチがありました。1 つは単に、外部のモデルをプロキシとして直接 .NET にインポートするというアプローチです。COM 相互運用がその一例です。.NET Framework の最初のリリース以来、COM 相互運用は、TLBIMP というツールによってこの方針を使用してきました。TLBIMP では、C# から直接使用できる .NET のプロキシ型が新しく作成されます。

C# 3.0 に付属する LINQ-to-SQL には、SQLMETAL というツールが含まれています。SQLMETAL は、クエリで使用する C# のプロキシ クラスに既存のデータベースをインポートします。また、Windows Management Instrumentation (WMI) クラスを C# にインポートするツールもあります。そして、多くのテクノロジによって、(しばしば属性と共に) C# を記述し、それから (LINQ-to-SQL、Windows Communication Foundation、シリアル化などの) 外部の操作の基盤として手書きのコードを使用して相互運用を実行できます。

2 つ目は、C# の型システムを完全に放棄し、コードに文字列とデータを埋め込むというアプローチです。このアプローチは、たとえば JScript オブジェクトのメソッドを呼び出すコードを記述するとき、または、ADO.NET アプリケーションに SQL クエリを埋め込むときに行います。.NET 自体と相互運用を実行する場合であっても、リフレクションを使用してバインドを実行時まで延期するときにも行います。

C# の dynamic キーワードは、こうした別のアプローチの煩わしさに対処するものです。ではまず、簡単な例としてリフレクションを見てみましょう。通常、リフレクションを使用するには、次のような定型のインフラストラクチャ コードが数多く必要です。

object o = GetObject();
Type t = o.GetType();
object result = t.InvokeMember("MyMethod", 
  BindingFlags.InvokeMethod, null, 
  o, new object[] { });
int i = Convert.ToInt32(result);

dynamic キーワードがあれば、この方法でリフレクションを使用してなんらかのオブジェクトの MyMethod メソッドを呼び出す代わりに、o を動的に処理して、実行時まですべての分析を遅延するようコンパイラに指定できます。そのコードは次のようになります。

dynamic o = GetObject();
int i = o.MyMethod();

このコードは機能し、複雑さがずっと軽減されたコードで先ほどと同じことが実行されます。

この短い簡略化された C# の構文は、JScript オブジェクトでの操作をサポートする ScriptObject クラスを見ると、おそらくもっとわかりやすくなります。ScriptObject クラスには、多くの異なるパラメーターを持つ InvokeMember メソッドがあります (パラメーターの数がそれよりも少ない、Invoke メソッドのある Silverlight は例外です。メソッド名も異なっているので注目してください)。どのパラメーターも、IronPython オブジェクトか IronRuby オブジェクトのメソッド、または、C# ではない、任意の数の通信するオブジェクトのメソッドを呼び出すのに必要なパラメーターとは異なります。

動的言語のオブジェクトに加え、本質的に動的である多様なデータ モデルがあり、データ モデルには、それらをサポートするさまざまな API (HTML DOM、System.Xml DOM、XML 用の XLinq モデルなど) があります。多くの場合、COM オブジェクトは動的で、なんらかのコンパイラの分析が実行時まで延期されることによるメリットがあります。

本質的に C# 4.0 では、簡単で一貫性のある、動的な操作の概要が提供されます。これを活用するために必要なのは、特定の値が動的であることを指定して、その値のすべての操作の分析が実行時まで延期されるようにすることだけです。

C# 4.0 では、dynamic はビルトイン型です。また、ビルトイン型であることを表わす特別な擬似キーワードです。ですが、dynamic は var とは異なることに注意してください。var で宣言される変数は厳密に型指定され、コンパイラがその型を認識します。これに対して、プログラマが dynamic を使用するときは、どの型が使用されるかをコンパイラが認識せず、ランタイムが判断します。

dynamic と DLR

こうした実行時の動的な操作をサポートするインフラストラクチャを、動的言語ランタイム (DLR) といいます。この新しい .NET Framework 4 ライブラリは、別のマネージ ライブラリと同じように、CLR 上で実行されます。DLR には、動的操作を開始した言語と、動的操作が行われるオブジェクトとの間で、動的操作を仲介する役割があります。動的操作が行われるオブジェクトでその操作が処理されない場合は、C# のコンパイラのランタイム コンポーネントがバインドを処理します。簡略化した完全ではないアーキテクチャ図を図 1 に示します。

CLR 上で実行される DLR

図 1 CLR 上で実行される DLR

動的メソッド呼び出しなどの動的操作に関する興味深い点は、レシーバー オブジェクトが実行時に自身をバインドでき、その結果としてあらゆる動的操作のセマンティクスを完全に決定できることです。たとえば、次のコードを見てみましょう。

dynamic d = new MyDynamicObject();
d.Bar("Baz", 3, d);

MyDynamicObject が次のように定義されたとしたら、何が起こるか想像がつくと思います。

class MyDynamicObject : DynamicObject {
  public override bool TryInvokeMember(
    InvokeMemberBinder binder, 
    object[] args, out object result) {

    Console.WriteLine("Method: {0}", binder.Name);
    foreach (var arg in args) {
      Console.WriteLine("Argument: {0}", arg);
    }

    result = args[0];
    return true;
  }
}

実際には、コードからは次のように出力されます。

Method: Bar
Argument: Baz
Argument: 3
Argument: MyDynamicObject

d を dynamic 型として宣言することで、MyDynamicObject インスタンスを使用するコードでは、d が関与する操作に対するコンパイル時のチェックが事実上無効になります。dynamic を使用するのは、「この型は何になるかわからないので、今のところはどんなメソッドやプロパティがあるかわかりません。コンパイラさん、全部素通りして、実行時にオブジェクトがあった場合は認識してください」と言うのと同じです。したがって、Bar への呼び出しは、コンパイラがその意味を認識していなくてもコンパイルされます。その後実行時に、オブジェクト自体が、この Bar への呼び出しに何をすべきか問われます。TryInvokeMember ではこの処理が行われます。

では、MyDynamicObject の代わりに、次のように Python オブジェクトを使用したとしましょう。

dynamic d = GetPythonObject();
d.bar("Baz", 3, d);

オブジェクトがここに表示されているファイルだったら、コードは先ほどと同じく機能し、出力も次のようにほとんど同じです。

def bar(*args):
  print "Method:", bar.__name__
  for x in args:
    print "Argument:", x

内部では、コンパイラによって、各 dynamic 値の使用に、DLR 呼び出しサイトを初期化および使用する一連のコードが生成されます。呼び出しサイトには、メソッド名、操作がチェック済みのコンテキストで行われたかどうかといった他のデータ、引数とその型に関する情報などの、実行時にバインドするために必要なすべての情報が含まれています。

このコードを保守する必要があると、先ほどのリフレクション コード、ScriptObject のコード、または XML クエリを含む文字列とまったく同じくらい見にくくなります。C# の dynamic 機能の重要な点はそこにあります。つまり、この機能を使用すれば、そのようなコードを記述しなくて済みます。

dynamic キーワードを使用するときは、単純なメソッド呼び出しや、インデクサーへの呼び出し、演算子 (+ など)、キャスト、複合 (+= や ++) など、コードをほぼ望みどおりの外観にできます。ステートメントでも、if(d) や foreach(var x in d) のように dynamic 値を使用できます。d && ShortCircuited や d ?? ShortCircuited など、コードで省略もサポートされます。

DLR によってこうした操作に共通インフラストラクチャが提供されることにより、コーディングを行う動的モデルごとに異なる API を扱う必要がなくなりました。API は 1 つしかなく、使用する必要さえもありません。API は、C# のコンパイラによって使用されるため、他に必要なコードの記述に時間をかけることができます。また、保守しなくてはならないインフラストラクチャ コードが減るため、生産性も向上します。

C# 言語では、動的オブジェクトを定義する簡単な方法は提供されません。C# での dynamic の役割は、動的オブジェクトの "使用" に尽きます。次のコードについて考えてみましょう。

dynamic list = GetDynamicList();
dynamic index1 = GetIndex1();
dynamic index2 = GetIndex2();
string s = list[++index1, index2 + 10].Foo();

このコードには動的操作が数多くまとめられています。まず、index1 に動的前置インクリメントがあり、index2 に動的加算が行われています。それから、動的なインデクサーの get が一覧で呼び出されます。これらの操作によって、Foo というメンバーが呼び出されます。最後に、この式の総合結果が文字列に変換され、s に格納されます。つまり、1 行に 5 つの動的な操作が含まれ、実行時にそれぞれがディスパッチされます。

動的操作のコンパイル時の型はどれも動的なので、その型は、計算から計算への "動的状態" のフローのようなものです。動的な式を複数含めなくても、動的操作は複数行われます。次の 1 行にも、先ほどと同じ 5 つの動的操作が含まれています。

string s = nonDynamicList[++index1, index2 + 10].Foo();

2 つのインデックス式の結果が動的なので、インデックス自体も動的です。そして、インデックスの結果が動的なので、Foo への呼び出しも動的です。それから、動的な値の文字列への変換が行われますが、もちろん、これも動的に行われます。オブジェクトは、変換要求を受けると、なんらかの特別な計算を実行する動的なオブジェクトになります。

先ほどの例で、C# が動的な式からあらゆる型への暗黙の変換を可能にしていることに注目してください。最後の文字列への変換は暗黙に行われ、明示的なキャスト操作が必要ありませんでした。同様に、あらゆる型を暗黙のうちに動的に変換できます。

この点では、dynamic はオブジェクトによく似ていますが、類似点はこれだけにとどまりません。コンパイラがアセンブリを生成して、動的変数を生成する必要があるとき、コンパイラは、型オブジェクトでその型オブジェクトを特別にマークすることで、それを実行します。ある意味では、dynamic はオブジェクトのエイリアスのようなものですが、使用されると、動的に操作を解決するという新しい動作も加わります。

次のように、dynamic と object のみが異なるジェネリック型間での変換を試みるとこれがわかります。List<dynamic> のインスタンスは実行時には List<object> のインスタンスなので、このような変換は常に機能します。

List<dynamic> ld = new List<object>();

次のように、object パラメーターで宣言されるメソッドのオーバーライドを試みると、dynamic と object 間の類似点もわかります。

class C {
  public override bool Equals(dynamic obj) { 
    /* ... */ 
  }
}

dynamic はアセンブリ内の修飾されたオブジェクトを解決しますが、私は dynamic を実際の型として考えることを好みます。dynamic では、別の型で可能なほとんどのことが実行できることを思い出せるためです。dynamic は型引数や、たとえば、戻り値として使用できます。例を挙げると、次の関数定義では、動的変数内に戻り値を格納しなくても、関数呼び出しの結果を動的に使用できます。

public dynamic GetDynamicThing() { 
  /* ... */ }

dynamic が処理およびディスパッチされる方法の詳細はここでは説明しきれませんが、機能を使用するだけなら詳細について知る必要はありません。重要な概念は、C# に似たコードを記述でき、コードに動的に記述した部分があるなら、コンパイラはその部分を実行時までそのままにするということです。

最後に取り上げる dynamic に関するトピックは、エラーです。コンパイラは、使用されている動的なものに Foo というメソッドが本当にあるかどうか確認できないため、エラーを生成できません。もちろん、だからと言って Foo への呼び出しが実行時に機能するわけではありません。機能はしますが、Foo というメソッドのないオブジェクトはたくさんあります。実行時に式のバインドに失敗したら、バインダーは、dynamic を使用していなかった場合にコンパイラが通知するであろうこととだいたい同じ例外をできる限り提供しようとします。

次のコードについて考えます。

try 
{
  dynamic d = "this is a string";
  d.Foo();
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
{
  Console.WriteLine(e.Message);
}

ここには、1 つの文字列と、明らかに Foo というメソッドのない複数の文字列があります。Foo を呼び出す行が実行されると、バインドに失敗して、RuntimeBinderException を取得します。このプログラムの出力は次のとおりです。

'string' does not contain a definition for 'Foo'

これは、まさに C# のプログラマが想定するエラー メッセージです。

名前付き引数とオプション パラメーター

また、C# では、メソッドは既定値を持つオプション パラメーターをサポートするようになり、このメソッドを呼び出すときにそれらのパラメーターを省略できます。次の Car クラスでこの動作を示します。

class Car {
  public void Accelerate(
    double speed, int? gear = null, 
    bool inReverse = false) { 

    /* ... */ 
  }
}

メソッドは、次の方法で呼び出すことができます。

Car myCar = new Car();
myCar.Accelerate(55);

これは次の呼び出しとまったく同じ結果になります。

myCar.Accelerate(55, null, false);

これは、省略したすべてのパラメーターにコンパイラが既定値を挿入するためです。

C# 4.0 では、いくつかの引数を名前で指定してメソッドを呼び出すこともできます。この方法では、オプション パラメーターに引数を渡す際に、その前にあるすべてのパラメーターにも引数を渡す必要がありません。

たとえば、Accelerate を呼び出して逆方向に移動させるとしましょう。ただし、gear パラメーターは指定しません。このためには、次のように実行します。

myCar.Accelerate(55, inReverse: true);

これは C# 4.0 の新しい構文で、次のように記述したのと同じです。

myCar.Accelerate(55, null, true);

実は、呼び出しているメソッド内のパラメーターがオプション パラメーターかどうかに関係なく、引数を渡すときに名前を使用できます。たとえば、次の 2 つの呼び出しはどちらも許容され、どちらも内容は同じです。

Console.WriteLine(format: "{0:f}", arg0: 6.02214179e23);
Console.WriteLine(arg0: 6.02214179e23, format: "{0:f}");

多くのパラメーターを受け取るメソッドを呼び出す場合は、コード内に含めるドキュメントとして名前を使用し、どのパラメーターがどれかを思い出せるようにすることもできます。

一見、オプション引数と名前付きパラメーターは、相互運用機能には見えません。どちらも、相互運用について考えなくても使用できます。この相互運用機能が追加されたのは、Office API で作業する人々を考慮してのことです。たとえば、Word のプログラミングや、ドキュメント インターフェイスの SaveAs メソッドのような簡単なメソッドについて考えてみましょう。このメソッドには 16 個のパラメーターがあり、すべてがオプション パラメーターです。以前のバージョンの C# では、このメソッドを呼び出すには次のようなコードを記述する必要があります。

Document d = new Document();
object filename = "Foo.docx";
object missing = Type.Missing;
d.SaveAs(ref filename, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);

C# 4.0 では、次のように記述できます。

Document d = new Document();
d.SaveAs(FileName: "Foo.docx");

これは、このような API で作業する人々のための改善点だと言えるでしょう。そして、Office プログラムを記述するプログラマの生活を向上することは、明らかに、言語に名前付き引数とオプション パラメーターを追加する動機となるものでした。

.NET ライブラリを記述して、オプション パラメーターのあるメソッドを追加することを考えているとき、オプション パラメーターを追加するか、C# のプログラマが何年も行ってきたオーバーロードを使用するかという選択肢に直面します。Car.Accelerate の例では、後者を選ぶことで次のような型を生成できます。

class Car {
  public void Accelerate(uint speed) { 
    Accelerate(speed, null, false); 
  }
  public void Accelerate(uint speed, int? gear) { 
    Accelerate(speed, gear, false); 
  }
  public void Accelerate(uint speed, int? gear, 
    bool inReverse) { 
    /* ... */ 
  }
}

記述しているライブラリに適したモデルを、自由に選択できます。C# にはこれまでオプション パラメーターがなかったため、(.NET Framework 4 を含む) .NET Framework ではオーバーロードが使用される傾向にあります。オプション パラメーターとオーバーロードを組み合わせる場合、C# のオーバーロードの解決では、あらゆる状況でどのオーバーロードを呼び出すかを決定する、明確なルールが使用されます。

インデックス付きプロパティ

C# 4.0 のいくつかの小さな言語機能は、COM 相互運用機能 API に対してコードを記述する場合のみサポートされます。先ほど示した Word の相互運用も 1 つの例です。

C# コードには、インデクサーの概念として、インデクサーをあるクラスに追加するとそのクラスのインスタンスの [] 演算子を事実上オーバーロードできるという考え方が常にあります。インデクサーに名前がなく、呼び出すときに名前が必要ないので、この概念を既定のインデクサーとも呼びます。いくつかの COM API には、既定ではないインデクサーもあります。つまり、それらのインデクサーは事実上、[] を使用するだけでは呼び出すことができず、名前を指定する必要があります。または、インデックス付きプロパティを、他の引数をいくつか受け取るプロパティとして考えることもできます。

C# 4.0 では、COM 相互運用型のインデックス付きプロパティがサポートされます。C# では、インデックス付きプロパティを持つ型を定義できませんが、COM 型ではインデックス付きプロパティを使用できます。これを実行する C# コードがどのようなになるかを、次に示す Excel ワークシートの Range プロパティで示します。

using Microsoft.Office.Interop.Excel;

class Program {
  static void Main(string[] args) {
    Application excel = new Application();
    excel.Visible = true;

    Worksheet ws = 
      excel.Workbooks.Add().Worksheets["Sheet1"];
    // Range is an indexed property
    ws.Range["A1", "C3"].Value = 123; 
    System.Console.ReadLine();
    excel.Quit();
  }
}

この例では、Range["A1", "C3"] は、インデックスとして使用できるものを返す Range というプロパティではありません。Range["A1", "C3"] は、Range アクセサーへの呼び出しで、A1 と C3 を Range アクセサーと共に渡します。そして、Value はインデックス付きプロパティのようには見えませんが、これもインデックス付きプロパティです。引数はすべて省略可能です。Range はインデックス付きプロパティなので、引数は、まったく指定しないこともできます。C# でインデックス付きプロパティがサポートされる前は、次のような呼び出しを記述していたはずです。

ws.get_Range("A1", "C3").Value2 = 123;

Value2 は、インデックス付きプロパティの Value が C# 4.0 より前のバージョンでは機能しないため単に追加したプロパティです。

COM の呼び出しサイトからの ref キーワードの省略

いくつかの COM API は、参照によって渡される多くのパラメーターで記述されていました。これは、実装がそれらのパラメーターに書き込まない場合でも同じです。Office スイートの中でも、COM API がこれをすべて実行する Word は特にわかりやすい例です。

そのようなライブラリを扱う必要があり、引数を参照によって渡す場合は、ローカル変数かローカル フィールドではない式を渡すことができず、これは大きな悩みの種です。Word の SaveAs の例で、この動作を確認できます。この例では、SaveAs メソッドを呼び出すためだけに filename というローカル変数と missing というローカル変数を宣言する必要がありました。これは、それらのパラメーターを参照によって渡す必要があるためです。

Document d = new Document();
object filename = "Foo.docx";
object missing = Type.Missing;
d.SaveAs(ref filename, ref missing, // ...

その後の新しい C# コードでは、次のように filename というローカル変数を宣言していないのにお気付きだと思います。

d.SaveAs(FileName: "Foo.docx");

これは、ref を省略する、新しい COM 相互運用機能によって可能になります。これで、COM 相互運用機能メソッドを呼び出す場合は、参照ではなく値によって引数を渡すことができます。値によって引数を渡すと、コンパイラは一時的にローカル変数を作成し、必要に応じてそのローカル変数を参照によって渡します。もちろん、メソッドが引数を変化させるとメソッド呼び出しの効果は確認できません。効果を確認するには、引数を ref で渡してください。

これにより、このような API を使用するコードがずっとわかりやすくなります。

COM 相互運用型の埋め込み

これは、C# の言語機能というよりは C# のコンパイラ機能ですが、COM 相互運用アセンブリを、実行時に存在させなくても使用できるようになりました。この目的は、アプリケーションで COM 相互運用アセンブリを展開する負荷を軽減することです。

COM 相互運用が .NET Framework の元のバージョンに搭載されたとき、プライマリ相互運用機能アセンブリ (PIA) の概念が作り出されました。これは、コンポーネント間で COM オブジェクトを共有する問題を解決する試みでした。Excel ワークシートを定義した相互運用アセンブリが異なる場合は、.NET 型が異なるため、コンポーネント間でそれらのワークシートを共有できません。PIA は、自身を一度だけ存在させることでこれを解決しました。これにより、すべてのクライアントがそれを使用することで、.NET 型は常に一致するようになりました。

理論上はすばらしい考えですが、実行してみると、PIA の展開は悩みの種になります。PIA は 1 つしかないのに、複数のアプリケーションがインストールやアンインストールを試みるからです。PIA のサイズが大きいこと、Office では既定の Office インストールで PIA を展開しないこと、また、ユーザーは自身の相互運用アセンブリを作成するために TLBIMP を使用するだけで簡単にこの単一のアセンブリ システムを回避できることを考えると、この問題は複雑です。

したがって、この状況を改善するために、次の 2 点が実現されました。

  • まるで同じ .NET 型であるかのように同じ識別特性 (名前、GUID など) を共有する、構造的に同一である 2 つの COM 相互運用型を扱う高度な機能がランタイムに搭載されました。
  • C# のコンパイラは、コンパイル時に固有のアセンブリ内にある相互運用型を単に再現することで、この機能を活用します。これにより、実行時に相互運用アセンブリを存在させる必要がなくなりました。

スペースの都合上、いくつかの詳細は省略しなくてはなりませんが、この機能も dynamic と同じで、詳細について知らなくても問題なく使用できます。Visual Studio に相互運用型を埋め込むようコンパイラに指定するには、参照の Embed Interop Types プロパティを true に設定します。

C# チームは、これが COM アセンブリを参照するのに推奨されるメソッドになることを想定しているため、Visual Studio では、C# プロジェクトに追加された新しい相互運用参照向けに、このプロパティが既定で True に設定されます。コマンド ライン コンパイラ (csc.exe) を使用してコードをビルドし、相互運用型を埋め込む場合は、/R スイッチではなく /L スイッチを使用して当該の相互運用アセンブリを参照する必要があります。

この記事で紹介した機能はすべて、さらに多くの新しい議論の基になり、どのトピックも 1 つの記事にするにふさわしいものです。省略したり軽く触れたりした詳細も多くありますが、この記事が C# 4.0 の概要を知る良い機会となり、今後皆さんが詳細を知ってこれらの機能を活用されることを望みます。そして、もし機能を活用されるなら、皆さんのために設計された、生産性とプログラムの読みやすさにおけるメリットを享受できることを願っています。

Chris Burrows は、マイクロソフトの C# コンパイラ チームの開発者で、C# コンパイラに dynamic を実装しました。Chris は、Visual Studio の開発に 9 年間携わっています。

この記事のレビューに協力してくれた技術スタッフの Eric Lippert に心より感謝いたします。