CLR

.NET 4.5 基本クラス ライブラリの新機能

Immo Landwerth

 

Microsoft .NET Framework 基本クラス ライブラリ (BCL) は、すべての基盤となります。System.Int32 や System.String など、基本構成要素のいくつかは安定していてほとんど変わりませんが、それでもマイクロソフトはこの分野に多くの作業を費やしています。今回は、.NET Framework 4.5 の BCL に加えられた大きな強化点 (および、いくつか小さな強化点) について説明します。

この記事は、最終版の製品と API ではなく、.NET Framework 4.5 ベータ版を基にしているため、機能は変更されることがあります。

Windows Communication Foundation (WCF) や Windows Presentation Foundation (WPF) など、.NET Framework の他の分野の概要については、MSDN ライブラリの「.NET Framework 4.5 RC の新機能」(bit.ly/p6We9u、英語) を参照してください。

非同期プログラミングの簡略化

非同期 I/O を使用することにはいくつかメリットがあり、UI のブロックを避けたり、OS が必要とするスレッド数を減らすことができます。それでも、非同期プログラミングがかなり複雑なことから、非同期 I/O を利用する機会がなかった方もいるでしょう。最大の問題は、以前の非同期プログラミング モデル (APM) が Begin/End メソッドのペアを考慮して設計されていたことです。このパターンのしくみを理解するため、以下に示す、ストリームをコピーする簡単な同期メソッドを考えてみます。

public void CopyTo(Stream source, Stream destination)
{
  byte[] buffer = new byte[0x1000];
  int numRead;
  while ((numRead = source.Read(buffer, 0, buffer.Length)) != 0)
  {
    destination.Write(buffer, 0, numRead);
  }
}

以前の APM を使用してこのメソッドを非同期にするには、図 1 に示すようなコードを記述する必要があります。

図 1 従来の非同期手法でのストリームのコピー

public void CopyToAsyncTheHardWay(Stream source, Stream destination)
{
  byte[] buffer = new byte[0x1000];
  Action<IAsyncResult> readWriteLoop = null;
  readWriteLoop = iar =>
  {
    for (bool isRead = (iar == null); ; isRead = !isRead)
    {
      switch (isRead)
      {
        case true:
          iar = source.BeginRead(buffer, 0, buffer.Length, 
            readResult =>
          {
            if (readResult.CompletedSynchronously) return;
            readWriteLoop(readResult);
          }, null);
          if (!iar.CompletedSynchronously) return;
          break;
        case false:
          int numRead = source.EndRead(iar);
          if (numRead == 0)
          {
            return;
          }
          iar = destination.BeginWrite(buffer, 0, numRead, 
            writeResult =>
          {
            if (writeResult.CompletedSynchronously) return;
            destination.EndWrite(writeResult);
            readWriteLoop(null);
          }, null);
          if (!iar.CompletedSynchronously) return;
          destination.EndWrite(iar);
          break;
        }
      }
  };
  readWriteLoop(null);
}

非同期バージョンは、同期バージョンと比べて、たいへん理解しにくいのは明らかです。デリゲートが関係するときに、プログラミング言語の基本構成要素 (ループなど) を機能させる必要がある定型コードからその意図をくみ取るのは困難です。まだピンとこない方は、例外処理とキャンセルを追加してみてください。

さいわい、今回の BCL のリリースでは、Task と Task<T> に基づく新しい非同期プログラミング モデルが導入されました。C# と Visual Basic は、async と await というキーワードを加えることで、すばらしい言語サポートが加わりました (ちなみに F# には、非同期ワークフローによる言語サポートが既に存在していて、実のところ、この機能から影響を受けています)。結果、これまで自分で記述しなければならなかった定型コードの全部とはいかないまでもほとんどが、コンパイラによって対処されるようになりました。.NET Framework に追加された新しい言語サポートと特定の API によって、非同期メソッドが、同期コードと事実上同じくらい簡単に記述できるようになります。以下の実際例を参照してください。CopyTo メソッドを非同期にするには、以下のように変更するだけです。

public async Task CopyToAsync(Stream source, Stream destination)
{
  byte[] buffer = new byte[0x1000];
  int numRead;
  while ((numRead = await 
     source.ReadAsync(buffer, 0, buffer.Length)) != 0)
  {
    await destination.WriteAsync(buffer, 0, numRead);
  }
}

新しい言語機能を使用する非同期プログラミングについて説明しておきたいことはもっとたくさんありますが、今回は、BCL そのものと、BCL の利用者にどのような影響があるかという点に絞っています。ここで説明しなかったことに興味がある方は、MSDN ライブラリの「Async と Await による非同期プログラミング (C# と Visual Basic)」(bit.ly/nXerAc、英語) を参照してください。

独自の非同期操作を作成するには、低レベルのビルド ブロックが必要です。このビルド ブロックに基づいて、先ほどの CopyToAsync メソッドのような、より複雑なメソッドを作成することができます。CopyToAsync メソッドには、ビルド ブロックとして、Stream クラスの ReadAsync メソッドと WriteAsync メソッドのみが必要です。図 2 は、BCL に追加された最も重要な非同期 API をいくつか示しています。

図 2 BCL の非同期メソッド

メソッド
System.IO.Stream

ReadAsync

WriteAsync

FlushAsync

CopyToAsync

System.IO.TextReader

ReadAsync

ReadBlockAsync

ReadLineAsync

ReadToEndAsync

System.IO.TextWriter

WriteAsync

WriteLineAsync

FlushAsync

TextReader.Peek のような、非常にレベルの細かい API の非同期バージョンは追加されませんでした。非同期 API はいくぶんオーバーヘッドも追加されるので、開発者が無意識のうちに誤った方向に進むのを防ぐためです。具体的には、BinaryReader メソッドや BinaryWriter メソッドの非同期バージョンは追加されませんでした。これらの API を使用するには、Task.Run を使用して新たに非同期操作を開始し、その非同期操作から同期 API を使用することをお勧めします。ただし、メソッド呼び出し単位には行わないようにしてください。一般的なガイダンスとして、非同期操作をできるだけ大きなブロックにすることが推奨されます。たとえば、BinaryReader を使用してストリームから 1,000 個の Int32 を読み取る場合は、1,000 個すべてを同期読み取りするために 1 つの Task を実行して待機する方が、Int32 を 1 つずつ読み取る 1,000 個の Task を実行して待機するよりも適切です。

非同期コードの記述の詳細については、.NET ブログ (blogs.msdn.com/b/pfxteam、英語) で並列プログラミングを参照してください。

読み取り専用コレクションのインターフェイス

長期にわたって BCL に求められていた機能の 1 つが、読み取り専用コレクションのインターフェイスでした (変更不可のコレクションと混同しないようにしてください。詳細については「読み取り専用コレクションの異なる概念」を参照してください)。(読み取り専用であるという) この特定の側面は、オプションの機能パターンを使用すれば最も適切にモデル化されるというのが、マイクロソフトの見解です。このパターンでは、使用者が特定機能のサポートの有無をテストして、NotSupportedException をスローできるように API が提供されています。

このパターンのメリットは、機能の組み合わせをモデル化しなくてもよいため、必要な型が少ない点にあります。たとえば、Stream クラスは複数の機能を提供し、そのすべてがブール型の get アクセサー (CanSeek、CanRead、CanWrite、および CanTimeout) によって表現されます。これにより、BCL がストリームに 1 つの型を持ち、それでいてストリーミング機能のあらゆる組み合わせをサポートできるようになります。

マイクロソフトは、数年かけて、複雑さが増えても、読み取り専用コレクションのインターフェイスを追加することに価値があるという結論に至りました。ここでは、まず、このインターフェイスについて紹介してから、インターフェイスが提供する機能について掘り下げます。図 3 に、既存の (変更可能な) コレクション インターフェイスの Visual Studio クラス ダイアグラムを示します。図 4 には、対応する読み取り専用のインターフェイスを示します。

Mutable Collection Interfaces
図 3 変更可能なコレクションのインターフェイス

Read-Only Collection Interfaces
図 4 読み取り専用コレクションのインターフェイス

IReadOnlyCollection<T> は、.NET Framework 4.5 ベータ版にはまだ追加されていないため、見つからなくても驚くことはありません。ベータ版では、IReadOnlyList<T> と IReadOnlyDictionary<TKey,TValue> は IEnumerable<T> から直接派生し、各インターフェイスは、それぞれ独自の Count プロパティを定義します。

IEnumerable<T> は共変です。つまり、メソッドが IEnumerable<Shape> を受け入れたら、このメソッドは IEnumerable<Circle> を指定して呼び出すことができます (Circle が Shape から派生している場合)。これは、さまざまな図形を描画するアプリケーションなど、特定の型で操作できる型階層とアルゴリズムがあるシナリオに便利です。IEnumerable<T> は、型のコレクションを扱うほとんどのシナリオで十分機能しますが、ときに以下のようなさらなる能力が必要になることもあります。

  1. Materialization.IEnumerable<T> は、コレクションが既に使用可能 ("具体化" されている) かどうか、または (コレクションが LINQ クエリを表す場合など) コレクションが反復処理されるたびに計算されるかどうかを表現することを許可しません。アルゴリズムで、コレクションを複数回反復処理しなければならないときにシーケンス計算の負荷が高いと、この表現によってパフォーマンスが低下することがあります。また、以降の処理でオブジェクトが再生成されるときの ID の不一致が原因で小さなバグを誘発することもあります。
  2. Count.IEnumerable<T> は、カウントを提供しません。実際、無限シーケンスになる可能性もあるため、1 つも保有しない場合もあります。ただし、ほとんどの場合、Enumerable.Count という静的拡張メソッドで十分です。このメソッドはシーケンス全体を反復処理することを避けるために ICollection<T> などの既知のコレクション型を特別扱いするという理由が 1 つ、結果の計算の負荷が高くない場合が多いという理由が 2 つです。ですが、コレクションのサイズによっては、Count.IEnumerable<T> の方が適切な場合もあります。
  3. Indexing.IEnumerable<T> は、項目へのランダム アクセスを許可しません。クイック ソートなどのアルゴリズムの中には、インデックスを使って項目にアクセスできることに依存するものもあります。この場合も、特定の Enumerable の後に IList<T> が付いている場合に最適化されたコード パスを使用する静的な拡張メソッド (Enumerable.ElementAt) があります。ただし、インデックス処理をループ内で使用している場合、線形走査がパフォーマンスに深刻な影響を与える場合があります。線形走査は、優れた O(n) アルゴリズムを O(n2) アルゴリズムに変えてしまうためです。そのため、ランダム アクセスを使おうとするときは、本当に必要かどうか考えてみてください。

IEnumerable<T> ではなく単純に ICollection<T>/IList<T> を使用しない理由は、共変性が失われるため、およびコレクションの読み取りだけを行うメソッドと、コレクションの変更も行うメソッドの区別を付けられなくなるためです。一般に非同期プログラミングまたはマルチスレッド処理を使用する際に、これは特に重要になります。言い換えれば、2 つの理由でメリットがあります。

今度は IReadOnlyCollection<T> と IReadOnlyList<T> について見てみます。IReadOnlyCollection<T> は、基本的に IEnumerable<T> と同じですが、Count プロパティがある点が異なります。これによって、具体化されたコレクションや、サイズが有限である既知のコレクションの必要性を表現できるアルゴリズムが作成可能になります。IReadOnlyList<T> は、インデクサーを追加して、さらに拡張します。どちらのインターフェイスも共変なので、メソッドが IReadOnlyList<Shape> を受け入れたら、以下のように List<Circle> を指定してこのメソッドを呼び出すことができます。

class Shape { /*...*/ }
class Circle : Shape { /*...*/ }
void LayoutShapes(IReadOnlyList<Shape> shapes) { /*...*/ }
void LayoutCircles()
{
  List<Circle> circles = GetCircles();
  LayoutShapes(circles);
}

残念ながら、現在の型システムでは、T を入力として受け取るメソッドがない場合を除き、型 T を共変にすることができません。したがって、IReadOnlyList<T> に IndexOf メソッドを追加することは不可能です。マイクロソフトでは、共変がサポートされないことに比べれば、これは小さな犠牲だと考えています。

配列、List<T>、Collection<T>、ReadOnlyCollection<T> などのコレクションの組み込み実装はすべて、読み取り専用コレクションのインターフェイスも実装します。どのコレクションも、現在は読み取り専用コレクションとして扱うことが可能になったため、アルゴリズムは、再利用のレベルを制限することなく、より正確に目的を宣言でき、あらゆるコレクション型で使用可能になります。前の例では、LayoutShapes の使用者は List<Circle> を渡すことができましたが、LayoutShapes は、Circle の配列や Collection<Circle> も受け取ります。

これらのコレクション型のもう 1 つのメリットは、Windows ランタイム (WinRT) の操作にもたらされる優れたエクスペリエンスです。WinRT は、Windows.Foundation.Collections.IIterable<T> や Windows.Foundation.Collections.IVector<T> など、独自のコレクション型を提供します。CLR メタデータ レイヤーは、これらを直接、対応する BCL のデータ型に投影します。たとえば、.NET Framework から WinRT を使用する場合、IIterable<T> は IEnumerable<T> になり、IVector<T> は IList<T> になります。実は、Visual Studio と IntelliSense を使用している開発者は、WinRT のコレクション型が異なっていることがわかりません。WinRT は、読み取り専用のバージョン (IVectorView<T> など) も提供します。したがって、新しい読み取り専用のインターフェイスが、残っているパズルのピースを埋め、すべてのコレクション型が .NET Framework と WinRT の間で簡単に共有できるようになります。

読み取り専用コレクションの異なる概念

  • 変更可能 (または読み取り専用ではない): .NET で最もよく使われるコレクション型です。項目の読み取り、追加、削除、変更を可能にする List<T> などのコレクションです。
  • 読み取り専用: 外部からは変更できないコレクションです。ただし、このコレクションの概念は、コンテンツが決して変わらないことを保証するものではありません。たとえば、ディクショナリのキーと値のコレクションは、直接更新できませんが、ディクショナリに追加すると、キーと値のコレクションが間接的に更新されます。
  • 変更不可: 作成後は、決して変更されないことが保証されるコレクションです。これは、マルチスレッドに適したプロパティです。複雑なデータ構造が完全に変更不可であれば、バックグラウンド ワーカーに常に安全に渡すことができます。だれかによって同時に変更される可能性について心配する必要はありません。このコレクション型は、現在 Microsoft .NET Framework 基本クラス ライブラリ (BCL) では提供されません。
  • Freezable: このコレクションは、固定されるまで、変更可能なコレクションのように動作します。その後は、変更不可のコレクションのように動作します。BCL はこれらのコレクションを定義しませんが、Windows Presentation Foundation で見つけることができます。

Andrew Arnott が、コレクションの異なる概念について詳細に説明するすばらしいブログ記事を公開しています (bit.ly/pDNNdM、英語)。

.zip アーカイブのサポート

一般的な .zip アーカイブの読み取りと書き込みのサポートも、数年にわたり頻繁に要望されてきました。.NET Framework 3.0 以降では、Open Packaging Convention (bit.ly/ddsfZ7、英語) 形式のアーカイブの読み取りと書き込みをサポートしています。ただし、System.IO.Packaging は特定の仕様をサポートするよう調整されていて、通常、普通の .zip アーカイブの処理には使えませんでした。

今回のリリースでは、System.IO.Compression.ZipArchive によって、圧縮へのすばらしいサポートが追加されました。さらに、DeflateStream の実装によって、以前から存在していたパフォーマンスと圧縮の品質に関する問題がいくらか修正されました。DeflateStream クラスは、.NET Framework 4.5 から、広く普及している zlib ライブラリを使用するようになりました。結果として、以前のバージョンの .NET Framework よりも、デフレート アルゴリズムや (多くの場合) 小さな圧縮ファイルをより適切に実装できるようになりました。

ディスク上のアーカイブ全体を解凍するのに必要なのは、以下の 1 行のみです。

ZipFile.ExtractToDirectory(@"P:\files.zip", @"P:\files");

また、代表的な操作では、アーカイブ全体をメモリに読み取る必要がなくなりました。たとえば、大きなアーカイブから 1 つのファイルを解凍するには、次のようにします。

using (ZipArchive zipArchive = 
  ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Read))
{
  foreach (ZipArchiveEntry entry in zipArchive.Entries)
  {
    if (entry.Name == "file.txt")
    {
      using (Stream stream = entry.Open())
      {
        ProcessFile(stream);
      }
    }
  }     
}

この場合、.zip アーカイブの目次のみをメモリに読み取ります。解凍されているファイルは完全にストリーミングされるので、メモリに収まる必要はありません。これによって、たとえメモリに制限があっても、任意の大きさの .zip アーカイブの処理が可能になります。

.zip アーカイブの作成も、同様に機能します。ディレクトリから .zip アーカイブを作成するには、以下の 1 行を記述するだけです。

ZipFile.CreateFromDirectory(@"P:\files", @"P:\files.zip");

もちろん、手動で .zip アーカイブを構築することもでき、内部構造の完全な制御が可能になります。以下のコードは、C# のソース コード ファイルのみが追加されている (さらに、SourceCode というサブディレクトリも追加されている) .zip アーカイブを作成する方法を示します。

IEnumerable<string> files = 
  Directory.EnumerateFiles(@"P:\files", "*.cs");
using (ZipArchive zipArchive = 
  ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Create))
{
  foreach (string file in files)
  {
    var entryName = Path.Combine("SourceCode", Path.GetFileName(file));
    zipArchive.CreateEntryFromFile(file, entryName);
  }
}

ストリームを使用すると、入力が実際のファイルではない .zip アーカイブを構築することもできます。.zip アーカイブ自体も同様です。たとえば、ZipFile.Open の代わりに、ストリームを受け取る ZipArchive コンストラクターを使用することができます。これは、データベースに保存されているコンテンツからその場で .zip アーカイブを作成するだけでなく、ディスクの代わりに応答ストリームに直接結果を書き込む Web サーバーを構築する際などに便利です。

1 点、API デザインの詳細についてもう少し説明します。静的で便利なメソッドが ZipFile クラスで定義されている一方で、実際のインスタンスには ZipArchive 型があることに、おそらくお気付きだと思います。これはなぜでしょう。

マイクロソフトは、.NET Framework 4.5 から、移植性を念頭に置いて API をデザインし始めました。Windows 8 の .NET for Metro-style apps など、ファイル パスをサポートしない .NET プラットフォームもあります。このプラットフォームでは、機能ベースのモデルを有効にするために、ファイル システムのアクセスが仲介されます。通常の Win32 API では、ファイルの読み取りまたは書き込みは実行できません。代わりに、WinRT API を使用する必要があります。

既に示したように、.zip 機能自体はファイル パスをまったく必要としません。したがって、機能が以下の 2 つの部分に分割されました。

  1. System.IO.Compression.dll: このアセンブリには、汎用の .zip 機能が含まれます。ファイル パスはサポートしません。このアセンブリのメイン クラスは ZipArchive です。
  2. System.IO.Compression.FileSystem.dll: このアセンブリは、拡張メソッドと静的ヘルパーを定義する、静的クラスの ZipFile を提供します。これらの API は、ファイル システム パスをサポートする .NET プラットフォームの便利なメソッドによって、.zip 機能を補強します。

移植可能な .NET コードの記述の詳細については、MSDN ライブラリの「移植可能なクラス ライブラリ」(bit.ly/z2r3eM、英語) を参照してください。

その他の強化点

もちろん、これですべてではありません。リリースに向けて長期間活動してきたので、最も重要な機能を挙げるのは、愛しい我が子を紹介するのと同じように感じられるときがあります。以下に、十分にスペースは取れないけれどもぜひ紹介しておきたい分野について、いくつか紹介します。

AssemblyMetadataAttribute: .NET Framework では、属性について、どれだけ多くあっても足りないということを学びました (ちなみに .NET Framework 4 には、既に 300 個以上のアセンブリレベルの属性があります)。AssemblyMetadataAttribute は、文字列ベースのキーと値のペアをアセンブリと関連付けることができる、汎用のアセンブリ属性です。これを使えば、製品のホームページや、そのアセンブリを構築したソースに対応するバージョン管理ラベルを指すことができます。

WeakReference<T>: 既存の非ジェネリックの WeakReference 型には、問題が 2 つあります。まず、使用者が、ターゲットにアクセスする必要があるときは常にキャストしなければなりません。もっと深刻なのは、競合状態を誘発しがちな設計上の欠陥がある点です。具体的に言うと、WeakReference 型は、オブジェクトが有効かどうかを確認するための API (IsAlive) と、実際のオブジェクトにアクセスするための別個の API (Target) を公開します。WeakReference<T> は、アトミック形式で両方の操作を実行する単一の API として TryGetTarget を提供して、この問題を解決します。

Comparer<T>.Create(Comparison<T>): BCL は、IComparer<T> インターフェイス、または Comparison<T> デリゲートによる、コレクションの比較子を実装する 2 とおりの方法を提供します。IComparer<T> を Comparison<T> に変換するのは簡単です。ほとんどの言語が、メソッドからデリゲート型への暗黙の変換を提供するため、IComparer<T> Compare メソッドから Comparison<T> を簡単に構築することができます。ただし、その逆に変換する場合は、IComparer<T> を手動で実装しなければなりません。.NET Framework 4.5 では、Comparison<T> が指定されていれば、IComparer<T> の実装を提供する静的な Create メソッドが Comparer<T> に追加されました。

ArraySegment<T>: .NET Framework 2.0 では、コピーすることなく特定の配列のサブセットを表すことができる構造体、ArraySegment<T> が追加されました。残念ながら、ArraySegment<T> はコレクション インターフェイスをまったく実装しなかったので、コレクション インターフェイスを操作するメソッドに渡すことができませんでした。最新リリースでは、その問題が解決され、ArraySegment<T> が、IEnumerable、IEnumerable<T>、ICollection<T>、IList<T> のほか、IReadOnlyCollection<T> と IReadOnlyList<T> も実装するようになりました。

SemaphoreSlim.WaitAsync: ロック取得の待機をサポートする、唯一の同期プリミティブです。この原理の詳細については、「What's New for Parallelism in .NET 4.5 Beta」(.NET 4.5 ベータ版の並列処理の新機能、bit.ly/AmAUIF、英語) を参照してください。

ReadOnlyDictionary<TKey,TValue>: .NET Framework 2.0 から、BCL には、特定のコレクション インスタンスの読み取り専用のラッパーとして機能する、ReadOnlyCollection<T> が導入されました。これにより、オブジェクト モデルの実装者は、使用者が変更できないコレクションを公開できるようになります。最新のリリースでは、ディクショナリにも同じ考え方が追加されました。

基になるストリームを破棄しないオプション、BinaryReader、BinaryWriter、StreamReader、StreamWriter: 上位レベルのリーダー クラスとライター クラスは、コンストラクターでストリーム インスタンスをすべて受け取ります。これは、前のリリースでは、そのストリームの所有権がリーダー/ライターのインスタンスに移ることを意味しました。つまり、リーダー/ライターの破棄によって、基になるストリームも破棄されました。これは、リーダー/ライターが 1 つしかないときには非常に便利です。ところが、ストリームに対して動作するけれども実装の一部にリーダー/ライターを使用しなければならない異なる API を複数構成する場合は、問題になります。前のリリースでは、リーダー/ライターを破棄せず、この問題を説明するコメントをソースに残すことが一番の解決策でした (また、このアプローチでは、データの損失を避けるために、ライターを手動でフラッシュする必要がありました)。.NET Framework 4.5 では、リーダー/ライターが基になるストリームを破棄してはならないことを明示的に指定できるパラメーター leaveOpen を受け取る、リーダー/ライター コンストラクターを使用して、このコントラクトを表現できるようになりました。

Console.IsInputRedirected、Console.IsOutputRedirected、および Console.IsErrorRedirected: コマンドライン プログラムは、入力と出力のリダイレクトをサポートします。これは、ほとんどのアプリケーションが意識されないところで行われます。ただし、リダイレクトがアクティブなとき、別の動作を希望する場合もあります。たとえば、色付きのコンソール出力は役に立たず、カーソル位置の設定がうまくいきません。この 3 つのプロパティは、標準のストリームがリダイレクトされたかどうかをクエリできるようにします。

Console.OutputEncoding と Console.InputEncoding が、Encoding.Unicode に設定できるようになった: Console.OutputEncoding を Encoding.Unicode に設定すると、コンソールに関連付けられている OEM コード ページで表すことができなかった文字をプログラムが記述できるようになります。また、この機能は、複数のスクリプトのテキストを簡単にコンソール表示できるようにします。詳細については、近日公開される、Console クラスに関する MSDN ライブラリの改訂ドキュメントを参照してください。

ExceptionDispatchInfo: エラー処理は、フレームワークのコンポーネントを構築するうえで重要な側面です。(C# では、"throw;" を使用して) 例外を再スローするだけでは十分ではない場合があります。と言うのも、これは例外ハンドラー内でのみ発生するためです。Task インフラストラクチャなどのいくつかのフレームワーク コンポーネントは、どこかの時点で例外を再スローする必要があります (元のスレッドにマーシャリングした後など)。以前、これは元のスタック トレースと Windows エラー報告 (WER) の分類 (別名 Watson バケット) が失われることを意味しました。それは、同じ例外オブジェクトをもう一度スローすると、この情報が上書きされるためです。ExceptionDispatchInfo は、既存の例外オブジェクトをキャプチャして、例外オブジェクトで記録された貴重な情報を失うことなく再スローすることを可能にします。

Regex.Timeout: 正規表現は、入力を検証する優れた方法です。しかし、特定のテキスト入力に適用されると計算の負荷がたいへん高くなる正規表現があることは、あまり知られていません。つまり、それらの正規表現は、時間に関する複雑さを急激に増加させます。これは、実際の正規表現が構成によって影響されるサーバー環境では特に問題になります。もし不可能でなかったとしても、特定の正規表現の実行時の動作を予測するのは困難なので、そのような状況に対する最も保守的なアプローチは、Regex エンジンが特定の入力を一致させるよう試みる長さに制約を課すことです。このため、Regex には、Regex.IsMatch、Regex.Match、Regex.Matches、Regex.Split、および Regex.Replace という、タイムアウトを受け取る API がいくつか追加されました。

ご意見をお寄せください

ここで紹介した機能は、Visual Studio 11 ベータ版で使用できます。Visual Studio 11 ベータ版は、プレリリース版のソフトウェアの高い基準を満たしているため、運用環境でサポートされます。ベータ版は https://www.microsoft.com/japan/visualstudio からダウンロードできます。これはプレリリース版のソフトウェアなので、何か問題を発見した場合 (https://connect.microsoft.com/VisualStudio/content/content.aspx?ContentID=25941) や、新機能についてのアイデアを思いついた場合 (visualstudio.uservoice.com、英語) は、ぜひフィードバックをお寄せください。私が所属するチームのブログ、blogs.msdn.com/b/bclteam (英語) を購読して、今後の変更や発表についての情報を得ることもお勧めします。

Immo Landwerth は、マイクロソフトの CLR チームのプログラム マネージャーで、Microsoft .NET Framework 基本クラス ライブラリ (BCL)、API デザイン、および移植可能なクラス ライブラリの分野で活動しています。Immo には BCL チームのブログ (blogs.msdn.com/b/bclteam、英語) から連絡を取ることができます。

この記事のレビューに協力してくれた技術スタッフの Nicholas BlumhardtGreg PaperinDaniel PlaistedEvgeny RoubinchteinAlok ShriramChris SzurgotStephen Toub、および Mircea Trofin に心より感謝いたします。