Share via


C#

C# 6.0 言語プレビュー

Mark Michaelis

このコラムが公開されるころは、Build (マイクロソフト デベロッパー カンファレンス) が終了し、多くの開発者が Build での発表内容にどのように対応しようか考えているところでしょう。発表内容をすぐに受け入れるか、わずかな不安を抱えつつ見守るか、現時点では見送るかなど、さまざまな対応が考えられます。.NET/C# 開発者であれば、C# コンパイラの次期バージョン ("Roslyn") がオープン ソースとしてリリースされるというのが、間違いなく最も重要な発表だったでしょう。このリリースでは、言語自体の強化も行われています。C# vNext (以下 C# 6.0。ただし、非公式名称なので注意してください) をすぐに使用する予定がなくても、その機能を知れば、今すぐ導入する価値があると考えるでしょう。

今回は、本コラムの執筆時点 (2014 年 3 月) での C# 6.0 で利用可能な機能、つまり roslyn.codeplex.com(英語) からダウンロードできるようになったオープン ソースに含まれる機能について詳しく説明します。ここではこのオープン ソースを 1 つのリリースとして扱い、「March Preview」と呼びます。この March Preview の C# 固有の機能は、最新の Microsoft .NET Framework やランタイムに依存することなく、コンパイラに完全に実装されています。つまり、開発または配置に使用している .NET Framework をアップグレードすることなく、C# 6.0 を開発環境に導入できます。実際、このリリースの C# 6.0 コンパイラのインストールは、Visual Studio 2013 の拡張機能のインストールとほとんど変わりません。MSBuild ターゲット ファイルが更新されるだけです。

C# 6.0 の各機能を紹介していくにつれて、開発者であれば次のようなことを考えるでしょう。

  • 以前に同じ機能をコーディングする妥当な手段があったか。機能がほぼ糖衣構文 (ショートカットや合理的な手法) であるという考え方です。たとえば、C# 5.0 にはプライマリ コンストラクターはありましたが、例外フィルターはありませんでした。
  • March Preview で使用できる機能かどうか。ここで説明するほとんどの機能は使用できますが、使用できない機能 (新しいバイナリ リテラルなど) もあります。
  • 新しい言語機能についてのフィードバックをチームに送る。まだリリース ライフサイクルの比較的初期の段階ですが、チームはこのリリースに関する意見を求めています (フィードバックの送り方については、msdn.com/Roslynを参照してください)。

このような疑問を思い浮かべることで、自身の開発作業における新機能の重要性を推し量ることができます。

インデックス付きメンバーと要素の初期化子

まず、図 1の単体テストについて考えます。

図 1 コレクション初期化子によるコレクションの代入 (C# 3.0 で追加)

using Microsoft.VisualStudio.TestTools.UnitTesting;using System.Collections.Generic;// ...[TestMethod]public void DictionaryIndexWithoutDotDollar(){  Dictionary builtInDataTypes =     new Dictionary()  {    {"Byte", "0 to 255"},    // ...    {"Boolean", "True or false."},    {"Object", "An Object."},    {"String", "A string of Unicode characters."},    {"Decimal", "±1.0 × 10e-28 to ±7.9 × 10e28"}  };  Assert.AreEqual("True or false.", builtInDataTypes["Boolean"]);}

構文によって少しわかりにくくなっていますが、図 1は名前と値のコレクションにすぎません。そのように考えると、<インデックス> = <値> のような非常にわかりやすい構文になります。C# 6.0 では、C# オブジェクト初期化子と新しいインデックス メンバー構文を使ってこれを可能にします。次のコードは、int ベースの要素の初期化子を示しています。

var cppHelloWorldProgram = new Dictionary{  [10] = "main() {",  [20] = "    printf(\"hello, world\")",  [30] = "}"};Assert.AreEqual(3, cppHelloWorldProgram.Count);

このコードでは、インデックスに整数を使用していますが、Dictionary は任意の型をインデックスとして使用できます (ただし、IComparable をサポートしている場合に限ります)。次の例では、インデックスのデータ型として文字列を使用し、インデックス付きメンバーの初期化子を使用して要素の値を指定しています。

Dictionary builtInDataTypes =  new Dictionary {    ["Byte"] = "0 to 255",    // ...    // Error: mixing object initializers and    // collection initializers is invalid    // {" Boolean", "True or false."},    ["Object"] = "An Object.",    ["String"] = "A string of Unicode characters.",    ["Decimal"] = "±1.0 × 10e?28 to ±7.9 × 10e28"  };

新しいインデックス メンバーの初期化の付属物として新しい $ 演算子があります。この文字列でインデックスを指定するメンバー構文は、具体的には文字列ベースのインデックスが普及するように用意されたものです。この新しい構文 (図 2 を参照) により、前の例で使用した文字列表記ではなく、メンバーの動的呼び出し (C# 4.0 で導入) のような構文で、要素の値を代入できます。

図 2 要素初期化子の一部としてインデックス付きメンバーの代入を使用してコレクションを初期化する

[TestMethod]public void DictionaryIndexWithDotDollar(){  Dictionary builtInDataTypes =     new Dictionary {    $Byte = "0 to 255",   // Using indexed members in element initializers    // ...    $Boolean = "True or false.",    $Object = "An Object.",    $String = "A string of Unicode characters.",    $Decimal = "±1.0 × 10e?28 to ±7.9 × 10e28"  };  Assert.AreEqual("True or false.", builtInDataTypes.$Boolean);}

$ 演算子を理解するため、AreEqual 関数呼び出しを見てみます。builtInDataTypes 変数での Dictionary メンバー "$Boolean" の呼び出しに注目します。Dictionary には "Boolean" メンバーを含めませんでした。$ 演算子はディクショナリのインデックス付きメンバーを呼び出しますが、これは buildInDataTypes["Boolean"] を呼び出すのと同じ結果になるため、明示的なメンバーは必要ありません。

文字列ベースの演算子と同様、ディクショナリに文字列インデックスの要素 ("Boolean" など) が存在するかどうかのコンパイル時検証は行われません。その結果、$ 演算子の後には、有効な C# (大文字と小文字が区別されます) のメンバー名を何でも指定できます。

インデックスでメンバーを指定する構文を十分に理解するため、XML、JSON、CSV、データベース ルックアップなど、型指定が厳密ではないデータ形式で、文字列インデックス指定が便利なところを考えてみます (Entity Framework による柔軟なコード生成は行われないものとします)。たとえば、図 3 では、Newtonsoft.Json フレームワークを使用して、文字列インデックスでメンバーを指定すると利便性が上がる例を示しています。

図 3 JSON データでインデックス付きメソッドを利用する

using Microsoft.VisualStudio.TestTools.UnitTesting;using Newtonsoft.Json.Linq;// ...[TestMethod]public void JsonWithDollarOperatorStringIndexers(){  // Additional data types eliminated for elucidation  string jsonText = @"    {      'Byte':  {        'Keyword':  'byte',        'DotNetClassName':  'Byte',        'Description':  'Unsigned integer',        'Width':  '8',        'Range':  '0 to 255'                },      'Boolean':  {        'Keyword':  'bool',        'DotNetClassName':  'Boolean',        'Description':  'Logical Boolean type',        'Width':  '8',        'Range':  'True or false.'                  },    }";  JObject jObject = JObject.Parse(jsonText);  Assert.AreEqual("bool", jObject.$Boolean.$Keyword);}

最後に 1 つ付け加えておきます。$ 演算子構文は、文字列型のインデックス (Dictionary など) でしか機能しません。

自動プロパティと初期化子

最近では、クラスの初期化はますます煩雑になっています。たとえば、項目のリスト用に System.Collections.Generic.List プライベート プロパティを内部で保持するカスタム コレクション型 (Queue など) の簡単な例を見てみましょう。コレクションのインスタンスを作成するときに、コレクションに含まれることになる項目のリストでキューを初期化する必要があります。ただし、プロパティを使用してこの処理を合理的に実行するには、初期化子またはその他のコンストラクターと共にバッキング フィールドを指定する必要があります。このような組み合わせを使用すると、必要なコードの量が事実上 2 倍になります。

C# 6.0 には、構文ショートカットとして、自動プロパティの初期化子が用意されています。そのため、次のようにして自動プロパティに直接代入できるようになります。

class Queue{  private List InternalCollection { get; } =     new List;   // Queue Implementation  // ...}

この場合、プロパティは読み取り専用であることに注意してください (setter は定義されません)。ただし、それでも、プロパティは宣言時に代入可能です。また、setter を備えた読み取り/書き込みプロパティもサポートされます。

プライマリ コンストラクター

C# 6.0 では、プロパティ初期化子と同じ考え方で、コンストラクター定義の構文ショートカットも用意されています。図 4 に示すような C# コンストラクターとプロパティ検証の使用を検討してください。

図 4 コンストラクターの共通パターン

[Serializable]public class Patent{  public Patent(string title , string yearOfPublication)  {    Title = title;    YearOfPublication = yearOfPublication;  }  public Patent(string title, string yearOfPublication,    IEnumerable inventors)    : this(title, yearOfPublication)  {    Inventors = new List();    Inventors.AddRange(inventors);  }  [NonSerialized] // For example  private string _Title;  public string Title  {    get    {      return _Title;    }    set    {      if (value == null)      {        throw new ArgumentNullException("Title");      }      _Title = value;    }  }  public string YearOfPublication { get; set; }  public List Inventors { get; private set; }  public string GetFullName()  {    return string.Format("{0} ({1})", Title, YearOfPublication);  }}

このコンストラクターの共通パターンには、いくつか注目すべき点があります。

  1. プロパティでは検証が必要になることから、基になるプロパティ フィールドを宣言しています。
  2. コンストラクター構文は、ありふれたパブリック クラス Patent{ public Patent(... の繰り返しでやや冗長になっています。
  3. 検証を含まないごく簡単なシナリオですが、大文字と小文字が異なるさまざまな "Title" が 7 回出現しています。
  4. プロパティの初期化には、コンストラクター内部からプロパティへの明示的な参照が必要です。

言語の特徴を損なわずに、このパターンの形式的な部分をいくつか取り除くため、C# 6.0 にはプロパティ初期化子とプライマリ コンストラクターが導入されています (図 5 参照)。

図 5 プライマリ コンストラクターを使用する

[Serializable]public class Patent(string title, string yearOfPublication){  public Patent(string title, string yearOfPublication,    IEnumerable inventors)    :this(title, yearOfPublication)  {    Inventors.AddRange(inventors);  }  private string _Title = title;  public string Title  {    get    {      return _Title;    }    set    {      if (value == null)      {        throw new ArgumentNullException("Title");      }      _Title = value;    }  }  public string YearOfPublication { get; set; } = yearOfPublication;  public List Inventors { get; } = new List();  public string GetFullName()  {    return string.Format("{0} ({1})", Title, YearOfPublication);  }}

プロパティ初期化子とプライマリ コンストラクター構文を組み合わせることで、C# コンストラクター構文が簡略化されます。

  • 自動プロパティには、読み取り専用プロパティ (getter のみを備えた Inventors プロパティを参照) または読み取り/書き込みプロパティ (setter とgetter の両方を備えた YearOfPublication プロパティを参照) がありますが、いずれにしても、プロパティ宣言の一部として初期値が代入されるように、プロパティの初期化をサポートします。この構文は、宣言時 (_Title に代入する宣言など) 、フィールドに既定値を代入する構文と同じです。
  • 既定では、プライマリ コンストラクターのパラメーターに、初期化子外部からアクセスすることはできません。たとえば、クラスで宣言される yearOfPublication フィールドはありません。
  • 読み取り専用プロパティ (getter のみ) でプロパティ初期化子を使用するときは、検証を指定する方法がありません (これは、基になる IL 実装で、プライマリ コンストラクターのパラメーターがバッキング フィールドに代入されることが原因です。自動プロパティに getter しか用意されていない場合、バッキング フィールドは IL で読み取り専用として定義されるため、特に注意が必要です)。
  • プライマリ コンストラクターを指定すると、常にコンストラクター チェーンの最後で実行されることになります (そのため、this(...) 初期化子を使用することはできません)。

次の例では、構造体の宣言について見てみます。ガイドラインには構造体の宣言は不変であるべきと示されています。次のコードは、プロパティ ベースの実装 (および特殊なパブリック フィールドの手法) を示しています。

struct Pair(string first, string second, string name){  public Pair(string first, string second) :     this(first, second, first+"-"+second)  {  }  public string First { get; } = second;  public string Second { get; } = first;  public string Name { get; } = name;  // Possible equality implementation  // ...}

Pair の実装の 2 つ目のコンストラクターは、プライマリ コンストラクターを呼び出します。一般に、すべての構造体コンストラクターは、this(...) 初期化子の呼び出しを使用して、直接または間接的にプライマリ コンストラクターを呼び出す必要があります。つまり、すべてのコンストラクターがプライマリ コンストラクターを直接呼び出す必要はありません。ただし、コンストラクター チェーンの最後にはプライマリ コンストラクターが呼び出されます。プライマリ コンストラクターが基本コンストラクターの初期化子を呼び出すため、最後に呼び出されることが必須になります。また、このようにすることで、よくある初期化のエラーに対する簡単な保護対策になります (ただし、コンストラクターを呼び出さなくても構造体のインスタンスを作成することはできます。これは C# 1.0 でも可能でした。たとえば、構造体の配列のインスタンスを作成すると、構造体のインスタンスが作成されます)。

カスタムの構造体またはクラス データ型のいずれのプライマリ コンストラクターでも、基本コンストラクターの呼び出しは、特定の基本クラス コンストラクターを呼び出すことによって、暗黙のうち (基本クラスの既定のコンストラクターが呼び出されます) または明示的に実行されます。後者の場合、特定の System.Exception コンストラクターを呼び出すカスタム例外では、プライマリ コンストラクターの後に目的のコンストラクターが指定されます。

class UsbConnectionException :   Exception(string message, Exception innerException,  HidDeviceInfo hidDeviceInfo) :base(message, innerException){  public HidDeviceInfo HidDeviceInfo { get;  } = hidDeviceInfo;}

プライマリ コンストラクターに関して 1 つ注意しなければならないのは、部分クラスで複数の (場合によっては互換性のない) プライマリ コンストラクターを使用しないようにすることです。部分クラスが複数の部分を構成している場合、クラス宣言を 1 つ指定するだけでプライマリ コンストラクターを定義できます。また、このプライマリ コンストラクターを使用するだけで、基本コンストラクターの呼び出しを指定できます。

プライマリ コンストラクターをこの March Preview で実装する場合、プライマリ コンストラクターに関して 1 つ重要な注意点があります。それはプライマリ コンストラクターのパラメーターの検証を実行する方法がないことです。また、プロパティ初期化子は自動プロパティにのみ有効であるため、プロパティの実装に検証を実装する方法もありません。これにより、インスタンス作成後に、パブリック プロパティの setter が無効なデータの代入にさらされるおそれもあります。当面検証が必要な場合は、プライマリ コンストラクターの機能を使用しないことが確実な回避策となります。

現時点では暫定案程度のものですが、フィールド パラメーターと呼ばれる検討中の関連機能があります。アクセス修飾子をプライマリ コンストラクターのパラメーターに含めると (プライベートな文字列 title など)、パラメーターは、title という名前のフィールドとして、名前の検索とパラメーターの大文字と小文字が確認され、クラス スコープにキャプチャされます。そのため、title は Title プロパティ内でも、他のインスタンスのクラス メンバーでも使用できます。さらに、アクセス修飾子を使用すると、読み取り専用などの追加の修飾子や、次のような属性など、フィールド構文全体を指定することができます。

public class Person(  [field: NonSerialized] private string firstName, string lastName)

アクセス修飾子を指定しないと、他の修飾子 (属性など) を使用することはできません。プライマリ コンストラクターのインラインでフィールド宣言が行われることを示すのがアクセス修飾子です。

(このコラムの執筆時点では、使用可能な機能にフィールド パラメーターの実装は含まれていませんでしたが、言語チームから Microsoft Build バージョンには含まれるという確証を得ています。この記事を読むころには、フィールド パラメーターを試してみることができるでしょう。ただし、この機能は比較的新しいため、遠慮なく msdn.com/Roslynまでフィードバックをお送りください。開発プロセスが進行して変更が困難になる前に検討します)。

静的 Using ステートメント

C# 6.0 のもう 1 つの「糖衣構文」機能は、静的な Using ステートメントの導入です。この機能により、静的メソッドを呼び出すときに、型の明示的な参照を取り除くことができます。また、名前空間内のすべての拡張メソッドではなく、特定のクラスの拡張メソッドのみに静的 Using を導入できます。図 6は、System.Console で静的 Using を使った "Hello World" の例を示しています。

図 6 静的 Using によるコードの簡略化

using System;using System.Console;public class Program{  private static void Main()  {    ConsoleColor textColor = ForegroundColor;    try    {      ForegroundColor = ConsoleColor.Red;      WriteLine("Hello, my name is Inigo Montoya... Who are you?: ");      ForegroundColor = ConsoleColor.Green;      string name = ReadLine(); // Respond: No one of consequence      ForegroundColor = ConsoleColor.Red;      WriteLine("I must know.");      ForegroundColor = ConsoleColor.Green;      WriteLine("Get used to disappointment");    }    finally    {      ForegroundColor = textColor;    }  }}

上記の例では、Console 修飾子が合計 9 個取り除かれています。確かに、例は不自然ですが、伝えたいことはお分かりいただけたでしょう。静的メンバー (プロパティを含む) で型プレフィックスを頻繁に使用することにあまり意味はありません。プレフィックスを取り除けば、コードは読み易くなり、コーディングも簡単になります。

March Preview では機能しませんが、静的 Using の 2 つ目の機能 (予定) を検討中です。この機能は、特定の型の拡張メソッドのみのインポートをサポートします。たとえば、拡張メソッドにたくさんの静的な型が含まれる utility 名前空間について考えてみてください。静的な Using がなければ、この名前空間の拡張メソッドをすべてインポートするか、まったくインポートしないかのいずれかになります。ただし、静的な Using があれば、名前空間全般ではなく、特定の型に対して使用可能な拡張メソッドをピンポイントで指定できます。結果として、System.Linq 名前空間全体を指定しなくても、Using System.Linq.Enumerable を指定するだけで、LINQ の標準クエリ演算子を呼び出すことができます。

残念ながら、静的な型しか静的 Using に対応していないため、(少なくとも March Preview では) 常にこのメリットを利用できるわけではありません。図 6 に Using System.ConsoleColor ステートメントがないのはこのためです。C# 6.0 の現状のプレビュー状態では、この制限を残すかどうか検討中です。皆さんはどう思われますか。

宣言式

ステートメントを記述しているときに、そのステートメントに必要な変数を宣言し忘れていることに気付くことがよくあります。次のような例が考えられます。

  • int.TryParse ステートメントをコーディングしているときに、解析結果を保存する out 引数用に変数を宣言する必要があることに気付く。
  • for ステートメントを記述しているときに、クエリを何回も実行し直さないように、コレクション (LINQ クエリの結果など) をキャッシュする必要があることに気付く。このため、for ステートメントのコーディングを中断して変数を宣言することになります。

このような面倒な問題に対処するため、C# 6.0 では宣言式が導入されます。つまり、変数の宣言が宣言ステートメントに限定されなくなり、式内でも変数を宣言できるようになります。図 7 に 2 つの例を示します。

図 7 宣言式の例

public string FormatMessage(string attributeName){  string result;  if(! Enum.TryParse(attributeName,     out var attributeValue) )  {    result = string.Format(      "'{0}' is not one of the possible {2} option combinations ({1})",      attributeName, string.Join(",", string[] fileAtrributeNames =      Enum.GetNames(typeof (FileAttributes))),      fileAtrributeNames.Length);  }  else  {    result = string.Format("'{0}' has a corresponding value of {1}",      attributeName, attributeValue);  }  return result;}

図 7で最初に注目するのは、attributeValue 変数を事前に別途個別に宣言するのではなく、Enum.TryParse の呼び出しでインラインで宣言されている点です。同様に、fileAttributeNames の宣言が string.Join の呼び出しで直接行われています。これだけで、同じステートメントの後半での Length へのアクセスが可能になります (string.Format 呼び出しで、fileAttributeNames.Length は書式指定文字列の前半で指定されていますが、{2} を置き換えるパラメータです。つまり、fileAttributeNames.Length にアクセスする前に fileAttributeNames を宣言します)。

宣言式のスコープはあまり厳密には定義されず、式を指定したステートメントのスコープになります。図 7 で、attributeValue のスコープは if-else ステートメントのスコープになります。そのため、条件の true ブロックでも false ブロックでもアクセスできます。同様に、fileAttributeNames は string.Format ステートメント呼び出しのスコープに一部だけ一致するため、if ステートメントの前半でのみ使用できます。

コンパイラは可能であれば、初期化子 (宣言の代入) からデータ型を推測して、宣言に暗黙に型指定される変数 (var) を使用できるようにします。ただし、out 引数の場合は、初期化子がなくても、呼び出し対象のシグネチャを使用して、暗黙に型指定される変数に対応することができます。ただし、推測は常に可能なわけではありません。また、読みやすさの面からは、最善の選択肢とは言えない場合があります。たとえば、図 7 の TryParse の場合、var が機能するのは、単に型引数 (FileAttributes) が指定されているからです。型引数がなければ var 宣言はコンパイルされず、代わりに明示的なデータ型が求められます。

Enum.TryParse(attributeName, out FileAttributes attributeValue)

図 7 の 2 つ目の宣言式の例では、string[] の明示的な宣言により、データ型が (List などではなく) 配列として特定されます。var の一般的な使用に関しては、結果のデータ型が明確ではないときは、暗黙に型指定された変数を避けるのが標準的なガイドラインです。

図 7の宣言式の例は、変数に代入する前にあるステートメントで宣言するだけで正しくコンパイルされます。

例外処理の強化

C# 6.0 には、2 つの新しい例外処理機能があります。1 つは async と await 構文の強化で、もう 1 つは例外フィルターのサポートです。

C# 5.0 で async と await の (コンテキスト) キーワードを導入したことで、開発者はタスクベースの非同期パターン (TAP) のコードを比較的簡単に作成できるようになりました。このパターンでは、C# コードを基になる一連の連続タスクに変換するための手間がかかる複雑な処理をコンパイラが行います。しかし残念ながら、このリリースでは、catch ブロックと finally ブロックで await 呼び出しを使用することはできませんでした。このような呼び出しが想像以上に必要となることが分かり、C# 5.0 の開発者は大掛かりな回避策を講じる必要がありました (awaiter パターンの利用など)。C# 6.0 ではこの問題点が取り除かれ、図 8に示すように、catch ブロックでも finally ブロックでも await 呼び出しを実行できるようになります (try ブロックでは既にサポートされていました)。

図 8 catch ブロック内での await 呼び出し

try{  WebRequest webRequest =    WebRequest.Create("http://IntelliTect.com");  WebResponse response =    await webRequest.GetResponseAsync();  // ...}catch (WebException exception){  await WriteErrorToLog(exception);}

C# 6.0 のもう 1 つの例外処理の強化 (例外フィルターのサポート) により、Visual Basic .NET や F# などの他の .NET 言語と肩を並べる最新言語になります。図 9 に、この機能の詳細を示します。

図 9 キャッチする例外をピンポイントに指定する例外フィルターの利用

using Microsoft.VisualStudio.TestTools.UnitTesting;using System.ComponentModel;using System.Runtime.InteropServices;// ...[TestMethod][ExpectedException(typeof(Win32Exception))]public void ExceptionFilter_DontCatchAsNativeErrorCodeIsNot42(){  try  {    throw new Win32Exception(Marshal.GetLastWin32Error());  }  catch (Win32Exception exception)     if (exception.NativeErrorCode == 0x00042)  {    // Only provided for elucidation (not required).    Assert.Fail("No catch expected.");  }}

catch 式の後に続く if 式に注目してください。catch ブロックでは、例外の種類が Win32Exception であること (または Win32Exception から派生すること) だけでなく、追加条件も検証されるようになります (この例では、エラー コードの特定の値を検証しています)。図 9の単体テストでは、例外の種類が一致したとしても catch ブロックでは例外はキャッチされず、代わりに、例外がエスケープされ、テスト メソッドの ExpectedException 属性で処理されるようになります。

これまで説明した C# 6.0 の他の機能 (プライマリ コンストラクターなど) と異なり、C# 6.0 以前には例外フィルターをコーディングする同じような手法は他にありませんでした。これまでは、特定の種類の例外をすべてキャッチし、例外コンテキストを明示的に確認して、現在の状態が有効な例外キャッチ シナリオでない場合に例外を再びスローするしか方法がありませんでした。つまり、C# 6.0 の例外フィルターにより、これまでの C# になかった機能が追加されます。

数値リテラル形式の追加

March Preview にはまだ実装されていませんが、C# 6.0 には数値リテラル (10 進数、16 進数、2 進数など) の桁を区切る桁区切り記号としてアンダースコア (_) が導入される予定です。シナリオにとって意味のあるグループに分割されるように、桁を区切ることができます。たとえば、次のように、整数の最大値を千単位で区切ることができます。

int number = 2_147_483_647;

桁を区切ることで、10 進数、16 進数、または 2 進数のいずれにもかかわらず、数値の大きさを把握しやすくなります。

桁区切り記号は、特に、C# 6.0 の新しい数値バイナリ文字列で役に立つと考えられます。すべてのプログラムで必要なものではありませんが、バイナリ文字列を利用すると、バイナリ ロジックやフラグベースの列挙型を使用する場合のメンテナンスが容易になります。たとえば、図 10 に示すような FileAttribute 列挙型があるとします。

図 10 列挙値にバイナリ リテラルを代入

[Serializable][Flags][System.Runtime.InteropServices.ComVisible(true)]public enum FileAttributes{  ReadOnly =          0b00_00_00_00_00_00_01, // 0x0001  Hidden =            0b00_00_00_00_00_00_10, // 0x0002  System =            0b00_00_00_00_00_01_00, // 0x0004  Directory =         0b00_00_00_00_00_10_00, // 0x0010  Archive =           0b00_00_00_00_01_00_00, // 0x0020  Device =            0b00_00_00_00_10_00_00, // 0x0040  Normal =            0b00_00_00_01_00_00_00, // 0x0080  Temporary =         0b00_00_00_10_00_00_00, // 0x0100  SparseFile =        0b00_00_01_00_00_00_00, // 0x0200  ReparsePoint =      0b00_00_10_00_00_00_00, // 0x0400  Compressed =        0b00_01_00_00_00_00_00, // 0x0800  Offline =           0b00_10_00_00_00_00_00, // 0x1000  NotContentIndexed = 0b01_00_00_00_00_00_00, // 0x2000  Encrypted =         0b10_00_00_00_00_00_00  // 0x4000}

バイナリ数値リテラルを使用すると、設定されているフラグと設定されていないフラグを明確に示すことができます。これは、コメントで示されているような 16 進数値や、次のようなコンパイル時にシフト手法に置き換えることができます。

Encrypted = 1<<14.

(この機能をすぐに試したい開発者の方は、March Preview リリースを使って Visual Basic .NET で試すことができます。)

まとめ

以上の言語の変更点を見るだけでも、C# 6.0 には大幅な変化や驚くような変化はないことがわかります。C# 6.0 を、C# 2.0 のジェネリック、C# 3.0 の LINQ、C# 5.0 の TAP などの他の重要なリリースと比較すると、C# 6.0 は主要なリリースというよりも "ドット" リリースと言えます (大きなニュースはコンパイラがオープン ソースとしてリリースされたことです)。ただし、C# コーディングに大きな変化をもたらしていないからといって、毎日使用しているとすぐに慣れてしまうコーディングの不便さや非効率さの解消に役立たなかったわけではありません。C# 6.0 の機能の中で特に私のお気に入りは、$ 演算子 (文字列インデックス メンバー)、プライマリ コンストラクター (フィールド パラメーターなし)、静的 Using、および宣言式です。これらの機能がすぐにコーディング時の既定の機能となり、場合によってはコーディング標準に追加されることを期待しています。

Mark Michaelis は、IntelliTect の創設者で、チーフ テクニカル アーキテクトとトレーナーを務めています。1996 年以来、C#、Visual Studio Team System (VSTS)、および Windows SDK の Microsoft MVP であり、2007 年には Microsoft Regional Director に認定されました。また、C#、Connected Systems Division、VSTS など、マイクロソフト ソフトウェアの設計レビュー チームにもいくつか所属しています。Michaelis は、開発者を対象としたカンファレンスで講演を行い、多数の記事や書籍を執筆しています。現在、Essential C# シリーズ (Addison-Wesley Professional) の新版の執筆に取り組んでいます。Michaelis は、イリノイ大学で哲学の学士号を、イリノイ工科大学でコンピューター サイエンスの修士号を取得しています。彼は、コンピューターに熱中していないときは、家族と時間を過ごしているか、新たな鉄人の育成にいそしんでいます。

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