C# Language
C#6.0の機能
サーチ…
前書き
C#言語のこの6番目の反復は、Roslynコンパイラによって提供されます。このコンパイラは、.NET Frameworkのバージョン4.6でリリースされましたが、以前のフレームワークバージョンを対象にして、後方互換性のある方法でコードを生成できます。 C#バージョン6のコードは、完全に下位互換性のある方法で.NET 4.0にコンパイルできます。以前のフレームワークでも使用できますが、追加のフレームワークサポートが必要な機能が正しく機能しないことがあります。
備考
C#の6番目のバージョンは、2015年7月にVisual Studio 2015と.NET 4.6とともにリリースされました。
新しい言語機能を追加するだけでなく、コンパイラの完全な書き換えも含まれています。以前はcsc.exe
はC ++で書かれたネイティブWin32アプリケーションcsc.exe
が、C#6ではC#で書かれた.NET管理アプリケーションになりました。この書き直しはプロジェクト "Roslyn"と呼ばれ、コードは現在オープンソースでGitHubで利用可能です。
オペレータ名
nameof
演算子は、コード要素の名前をstring
として返しstring
。これは、メソッドの引数に関連する例外やINotifyPropertyChanged
実装時に例外をスローするときに便利INotifyPropertyChanged
。
public string SayHello(string greeted)
{
if (greeted == null)
throw new ArgumentNullException(nameof(greeted));
Console.WriteLine("Hello, " + greeted);
}
nameof
演算子はコンパイル時に評価され、式を文字列リテラルに変更します。これは、それらのメンバーを公開する文字列の場合にも便利です。次の点を考慮してください。
public static class Strings
{
public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}
nameof
式はコンパイル時定数なので、属性、 case
ラベル、 switch
文などで使用できます。
Enum
nameof
を使用すると便利です。の代わりに:
Console.WriteLine(Enum.One.ToString());
以下を使用することができます。
Console.WriteLine(nameof(Enum.One))
どちらの場合も出力はOne
になります。
nameof
演算子は静的な構文を使用して非静的メンバーにアクセスできます。代わりに:
string foo = "Foo";
string lengthName = nameof(foo.Length);
次のものに置き換えることができます:
string lengthName = nameof(string.Length);
両方の例で、出力はLength
になります。しかし、後者は不要なインスタンスの作成を防ぎます。
演算子のnameof
はほとんどの言語構造で機能しますが、いくつかの制限があります。たとえば、開いているジェネリック型またはメソッドの戻り値に対して、 nameof
演算子を使用することはできません。
public static int Main()
{
Console.WriteLine(nameof(List<>)); // Compile-time error
Console.WriteLine(nameof(Main())); // Compile-time error
}
さらに、ジェネリック型に適用すると、ジェネリック型のパラメータは無視されます。
Console.WriteLine(nameof(List<int>)); // "List"
Console.WriteLine(nameof(List<bool>)); // "List"
より多くの例については、 nameof
専用のトピックを参照してください。
以前のバージョンの回避策( 詳細 )
nameof
演算子は、6.0より前のバージョンのC#では存在しませんが、次のようにMemberExpression
を使用することで同様の機能を実現できます。
式:
public static string NameOf<T>(Expression<Func<T>> propExp)
{
var memberExpression = propExp.Body as MemberExpression;
return memberExpression != null ? memberExpression.Member.Name : null;
}
public static string NameOf<TObj, T>(Expression<Func<TObj, T>> propExp)
{
var memberExpression = propExp.Body as MemberExpression;
return memberExpression != null ? memberExpression.Member.Name : null;
}
使用法:
string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);
この手法では、すべての呼び出しで式ツリーが作成されるため、コンパイル時に評価され、実行時にオーバーヘッドがゼロになるnameof
演算子と比較して、パフォーマンスが大幅に悪化することに注意してください。
式ボディ関数メンバー
式ボディ関数メンバは、ラムダ式をメンバ本体として使用できます。シンプルなメンバの場合、よりクリーンで読みやすいコードになります。
式本体関数は、プロパティ、インデクサ、メソッド、および演算子に使用できます。
プロパティ
public decimal TotalPrice => BasePrice + Taxes;
それと同等です:
public decimal TotalPrice
{
get
{
return BasePrice + Taxes;
}
}
式付きの関数をプロパティとともに使用すると、プロパティはゲッター専用のプロパティとして実装されます。
インデクサー
public object this[string key] => dictionary[key];
それと同等です:
public object this[string key]
{
get
{
return dictionary[key];
}
}
メソッド
static int Multiply(int a, int b) => a * b;
それと同等です:
static int Multiply(int a, int b)
{
return a * b;
}
void
メソッドでも使用できます。
public void Dispose() => resource?.Dispose();
ToString
のオーバーライドをPair<T>
クラスに追加ToString
ことができます:
public override string ToString() => $"{First}, {Second}";
さらに、この単純化されたアプローチは、 override
キーワードを使用して動作しoverride
。
public class Foo
{
public int Bar { get; }
public string override ToString() => $"Bar: {Bar}";
}
演算子
これはオペレータが使用することもできます:
public class Land
{
public double Area { get; set; }
public static Land operator +(Land first, Land second) =>
new Land { Area = first.Area + second.Area };
}
制限事項
エクスプレッションボディメンバーにはいくつかの制限があります。ブロックステートメントやブロックを含むステートメントを含むことはできません: if
、 switch
、 for
、 foreach
、 while
、 do
、 try
など
いくつかのif
文は三項演算子で置き換えることができます。一部のfor
およびforeach
文は、たとえばLINQクエリに変換できます。
IEnumerable<string> Digits
{
get
{
for (int i = 0; i < 10; i++)
yield return i.ToString();
}
}
IEnumerable<string> Digits => Enumerable.Range(0, 10).Select(i => i.ToString());
それ以外の場合は、関数メンバーの古い構文を使用することができます。
エクスプレッションボディメンバーはasync
/ await
含むことができますが、多くの場合、冗長です。
async Task<int> Foo() => await Bar();
次のものに置き換えることができます:
Task<int> Foo() => Bar();
例外フィルタ
例外フィルタを使用すると 、開発者は条件をboolean
式の形でキャッチブロックに追加し、条件がtrue
評価された場合にのみcatch
を実行することができtrue
。
例外フィルタは、元の例外でデバッグ情報を伝播させることができます。ここでは、 catch
ブロック内のif
文を使用して例外を再スローすると、元の例外のデバッグ情報の伝播が停止します。例外フィルタを使用すると、条件が満たされない限り 、例外は呼び出しスタック内で上方に伝播し続けます。その結果、例外フィルタにより、デバッグの作業がはるかに簡単になります。 throw
文で停止する代わりに、デバッガは現在の状態とすべてのローカル変数を保存したまま、例外をthrow
文で停止します。クラッシュダンプも同様の影響を受けます。
例外フィルタは初めからCLRでサポートされており、CL.NETの例外処理モデルの一部を公開することで、10年以上にわたってVB.NETとF#からアクセス可能になっています。 C#6.0のリリース後に限り、C#開発者にも機能が提供されています。
例外フィルタの使用
例外フィルタは、 catch
式にwhen
句を追加することによって利用されます。 when
節でbool
を返す任意の式を使用することができます( awaitを除く)。宣言されたException変数ex
は、 when
句からアクセス可能です。
var SqlErrorToIgnore = 123;
try
{
DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
throw new Exception("An error occurred accessing the database", ex);
}
when
節を持つ複数のcatch
ブロックを組み合わせることができます。最初のwhen
節がtrue
を返すと、例外が捕捉されます。そのcatch
ブロックは入力されますが、他のcatch
節は無視されます( when
節は評価されません)。例えば:
try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
//the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
//someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }
危険なwhen節
あぶない
とき:例外フィルタを使用するように危険なことができ
Exception
内からスローされたwhen
句、Exception
からのwhen
句は無視され、として扱われるfalse
。このアプローチにより、開発者は無効なケースを考慮しなくてもwhen
節を書くことができます。
次の例は、このようなシナリオを示しています。
public static void Main()
{
int a = 7;
int b = 0;
try
{
DoSomethingThatMightFail();
}
catch (Exception ex) when (a / b == 0)
{
// This block is never reached because a / b throws an ignored
// DivideByZeroException which is treated as false.
}
catch (Exception ex)
{
// This block is reached since the DivideByZeroException in the
// previous when clause is ignored.
}
}
public static void DoSomethingThatMightFail()
{
// This will always throw an ArgumentNullException.
Type.GetType(null);
}
例外フィルタは、失敗したコードが同じ関数内にある場合にthrow
を使用する際の混乱した行番号の問題を回避します。たとえば、この場合、行番号は3ではなく6と報告されます。
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) {
6. throw;
7. }
例外行番号は、6行目のthrow
文でキャッチして再スローされたため、6と報告されます。
例外フィルタでも同じことは起こりません。
1. int a = 0, b = 0;
2. try {
3. int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6. throw;
7. }
この例a
はa
は0で、 catch
節は無視されますが、3が行番号として報告されます。これはスタックを巻き戻さないためです。具体的には、5行目に例外がキャッチされないの a
、実際には0
であるため、6行目が実行されないため、6行目に例外が再スローされる機会がないからです。
副作用としてのロギング
この条件のメソッド呼び出しは副作用を引き起こす可能性があるため、例外フィルタを使用して例外を捕捉することなく例外を実行することができます。これを利用する一般的な例は、常にfalse
返すLog
メソッドです。これにより、例外を再スローする必要なく、デバッグ中にログ情報をトレースすることができます。
サードパーティのログアセンブリが使用されている場合は特に、これは、ログの快適な方法のようですが、それは危険なことができることに注意してください 。これらは、容易に検出されない可能性のある明白でない状況でログインしている間に例外をスローする可能性があります(前述の
when(...)
節を参照)。
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
// This catch block will never be reached
}
// ...
static bool Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
return false;
}
以前のバージョンのC#での一般的なアプローチは、例外のログと再スローでした。
try
{
DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
Log(ex, "An error occurred");
throw;
}
// ...
static void Log(Exception ex, string message, params object[] args)
{
Debug.Print(message, args);
}
finally
ブロック
finally
ブロックは、例外がスローされるかどうかに関係なく実行されます。 when
式を持つ1つの微妙な点が例外フィルタであり、内側のfinally
ブロックに入る前にスタックの上でさらに実行されます。これにより、コードがグローバル状態(現在のスレッドのユーザーやカルチャなど)を変更してfinally
ブロックに戻そうとすると、予期しない結果や動作が発生する可能性があります。
例: finally
ブロック
private static bool Flag = false;
static void Main(string[] args)
{
Console.WriteLine("Start");
try
{
SomeOperation();
}
catch (Exception) when (EvaluatesTo())
{
Console.WriteLine("Catch");
}
finally
{
Console.WriteLine("Outer Finally");
}
}
private static bool EvaluatesTo()
{
Console.WriteLine($"EvaluatesTo: {Flag}");
return true;
}
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
生産量:
開始
EvaluatesTo:True
最後にインナー
キャッチ
最後に外に
上記の例では、 SomeOperation
メソッドがグローバル状態の変更を呼び出し元のwhen
節に「リーク」させたくない場合は、状態を変更するcatch
ブロックも含める必要があります。例えば:
private static void SomeOperation()
{
try
{
Flag = true;
throw new Exception("Boom");
}
catch
{
Flag = false;
throw;
}
finally
{
Flag = false;
Console.WriteLine("Inner Finally");
}
}
IDisposable.Dispose
は、 using
ブロック内で呼び出された例外がスタックをバブリングする前に常に呼び出されるため、同じ目的を達成するためにブロックを使用するセマンティクスを活用するIDisposable
ヘルパクラスを参照することも一般的です。
自動プロパティ初期化子
前書き
プロパティは、終了後に=
演算子で初期化できます}
。以下のCoordinate
クラスは、プロパティを初期化するために使用できるオプションを示しています。
public class Coordinate
{
public int X { get; set; } = 34; // get or set auto-property with initializer
public int Y { get; } = 89; // read-only auto-property with initializer
}
可視性の異なるアクセサ
アクセサーに異なる可視性を持つ自動プロパティーを初期化することができます。保護された設定者の例を次に示します。
public string Name { get; protected set; } = "Cheeze";
アクセサもすることができinternal
、 internal protected
、またはprivate
。
読み取り専用のプロパティ
可視性の柔軟性に加えて、読み取り専用の自動プロパティを初期化することもできます。ここに例があります:
public List<string> Ingredients { get; } =
new List<string> { "dough", "sauce", "cheese" };
この例では、複合型のプロパティを初期化する方法も示しています。また、自動プロパティは書き込み専用にすることもできないため、書き込み専用の初期化も排除されます。
古いスタイル(pre C#6.0)
C#6より前では、より冗長なコードが必要でした。プロパティのバッキングプロパティと呼ばれる1つの余分な変数を使用して、デフォルト値を与えたり、以下のようなpublicプロパティを初期化したりしました。
public class Coordinate
{
private int _x = 34;
public int X { get { return _x; } set { _x = value; } }
private readonly int _y = 89;
public int Y { get { return _y; } }
private readonly int _z;
public int Z { get { return _z; } }
public Coordinate()
{
_z = 42;
}
}
注意: C#6.0より前では、コンストラクタ内から自動実装されたプロパティ (getterとsetterを持つプロパティ )の読み込みと書き込みを初期化できましたが、その宣言でインラインプロパティを初期化できませんでした
使用法
イニシャライザはフィールドイニシャライザと同様に静的な式に評価されなければなりません。非静的メンバーを参照する必要がある場合は、前と同じようにコンストラクターのプロパティを初期化するか、式付きのプロパティを使用できます。以下のような非スタティック式(コメントアウト)は、コンパイラエラーを生成します:
// public decimal X { get; set; } = InitMe(); // generates compiler error
decimal InitMe() { return 4m; }
しかし、静的メソッドを使って自動プロパティを初期化することができます:
public class Rectangle
{
public double Length { get; set; } = 1;
public double Width { get; set; } = 1;
public double Area { get; set; } = CalculateArea(1, 1);
public static double CalculateArea(double length, double width)
{
return length * width;
}
}
このメソッドは、アクセサーのレベルが異なるプロパティにも適用できます。
public short Type { get; private set; } = 15;
自動プロパティ初期化子では、宣言内でプロパティを直接割り当てることができます。読み取り専用プロパティの場合、プロパティが不変であることを保証するために必要なすべての要件を処理します。たとえば、次の例のFingerPrint
クラスを考えてみましょう。
public class FingerPrint
{
public DateTime TimeStamp { get; } = DateTime.UtcNow;
public string User { get; } =
System.Security.Principal.WindowsPrincipal.Current.Identity.Name;
public string Process { get; } =
System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
注意事項
オートプロパティまたはフィールドイニシャライザと、 =>
とは対照的に=
=>
を使用する同様の見た目の式ボディメソッドと混同しないように注意してください{ get; }
は含まれません{ get; }
。
たとえば、次の宣言はそれぞれ異なります。
public class UserGroupDto
{
// Read-only auto-property with initializer:
public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
// Read-write field with initializer:
public ICollection<UserDto> Users2 = new HashSet<UserDto>();
// Read-only auto-property with expression body:
public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}
行方不明{ get; }
は、プロパティ宣言のpublicフィールドになります。読み取り専用の自動プロパティUsers1
と読み取り/書き込みフィールドUsers2
は、一度だけ初期化されますが、パブリックフィールドではクラス外のコレクションインスタンスが変更されることがありますが、これは通常は望ましくありません。式本体を持つ読み取り専用自動プロパティーを初期化子で読み取り専用プロパティーに変更するには、 >
from =>
削除するだけでなく、 { get; }
。
Users3
の異なるシンボル( =>
代わりに=
)は、 HashSet<UserDto>
新しいインスタンスを返すプロパティへの各アクセスをもたらしますが、(コンパイラの観点から見て)有効なC#は、コレクションメンバーに使用されます。
上記のコードは次のものと同じです:
public class UserGroupDto
{
// This is a property returning the same instance
// which was created when the UserGroupDto was instantiated.
private ICollection<UserDto> _users1 = new HashSet<UserDto>();
public ICollection<UserDto> Users1 { get { return _users1; } }
// This is a field returning the same instance
// which was created when the UserGroupDto was instantiated.
public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();
// This is a property which returns a new HashSet<UserDto> as
// an ICollection<UserDto> on each call to it.
public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}
インデックス初期化子
索引イニシャライザを使用すると、索引付きのオブジェクトを同時に作成および初期化できます。
これにより、辞書の初期化は非常に簡単になります。
var dict = new Dictionary<string, int>()
{
["foo"] = 34,
["bar"] = 42
};
インデックス付きのゲッターまたはセッターを持つオブジェクトは、次の構文で使用できます。
class Program
{
public class MyClassWithIndexer
{
public int this[string index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
}
public static void Main()
{
var x = new MyClassWithIndexer()
{
["foo"] = 34,
["bar"] = 42
};
Console.ReadKey();
}
}
出力:
インデックス:foo、値:34
インデックス:bar、値:42
クラスに複数のインデクサがある場合は、それらをすべて単一のステートメントグループに割り当てることができます。
class Program
{
public class MyClassWithIndexer
{
public int this[string index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
public string this[int index]
{
set
{
Console.WriteLine($"Index: {index}, value: {value}");
}
}
}
public static void Main()
{
var x = new MyClassWithIndexer()
{
["foo"] = 34,
["bar"] = 42,
[10] = "Ten",
[42] = "Meaning of life"
};
}
}
出力:
インデックス:foo、値:34
インデックス:bar、値:42
インデックス:10、値:10
指数:42、価値:人生の意味
インデクサset
アクセサは、(コレクション初期化子で使用される) Add
メソッドとは異なる動作をする可能性があることに注意してAdd
。
例えば:
var d = new Dictionary<string, int>
{
["foo"] = 34,
["foo"] = 42,
}; // does not throw, second value overwrites the first one
対:
var d = new Dictionary<string, int>
{
{ "foo", 34 },
{ "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.
文字列補間
文字列補間により、 variables
とテキストを結合して文字列を形成することができます。
基本的な例
2つのint
変数foo
とbar
が作成されbar
。
int foo = 34;
int bar = 42;
string resultString = $"The foo is {foo}, and the bar is {bar}.";
Console.WriteLine(resultString);
出力 :
fooは34、barは42です。
文字列内の中括弧は引き続き次のように使用できます。
var foo = 34;
var bar = 42;
// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");
これにより、次の出力が生成されます。
fooは{foo}、バーは{bar}です。
逐語的な文字列リテラルによる補間の使用
文字列の前に@
を使用すると、文字列がそのまま解釈されます。したがって、たとえば、Unicode文字や改行は、入力したとおりにそのまま残ります。ただし、次の例のように、補間された文字列の式には影響しません。
Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
出力: それが明らかでなかった場合:
\ u00B9
フー
34、
とバー
42です。
式
文字列補間では、中括弧{}
内の式も評価できます。結果は文字列内の対応する位置に挿入されます。たとえば、 foo
とbar
最大値を計算して挿入するには、中括弧内でMath.Max
使用します。
Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");
出力:
そして、より大きなものは:42
注意:中かっこと式の間の先頭または末尾の空白(スペース、タブ、CRLF /改行を含む)は完全に無視され、出力には含まれません
別の例として、変数は通貨としてフォーマットできます。
Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");
出力:
Fooは、小数点以下4桁までの通貨としてフォーマットされました:$ 34.0000
または、日付として書式設定することもできます。
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
出力:
今日は:Monday、July、20 - 2015
条件付き(3値)演算子を持つステートメントも補間内で評価できます。ただし、これらは括弧で囲む必要があります。コロンは、上記のように書式を指定するために使用されるためです。
Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");
出力:
バーはfooよりも大きい!
条件式と書式指定子を混在させることができます:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
出力:
環境:32ビットプロセス
エスケープシーケンス
バックスラッシュ( \
)と引用符( "
)文字のエスケープは、補間されていない文字列とまったく同じように、逐語的および非逐語的な両方の文字列リテラルに対して機能します。
Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");
出力:
Fooは34です。非そのままの文字列では、\と\をバックスラッシュでエスケープする必要があります。
Fooは34です。逐語的な文字列では、 "余分な引用符でエスケープする必要がありますが、エスケープする必要はありません\
補間された文字列に中かっこ{
または}
を含めるには、2つの中括弧{{
または}}
ます。
$"{{foo}} is: {foo}"
出力:
{foo}は:34
FormattableString型
$"..."
文字列補間式の型は、 必ずしも単純な文字列ではありません 。コンパイラは、コンテキストに応じてどのタイプを割り当てるかを決定します。
string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";
これは、コンパイラがどのオーバーロードされたメソッドが呼び出されるかを選択する必要がある場合にも、タイプの優先順位です。
新しい型の System.FormattableString
、書式設定される引数とともに、複合書式文字列を表します。これを使用して、補間引数を処理するアプリケーションを具体的に記述します。
public void AddLogItem(FormattableString formattableString)
{
foreach (var arg in formattableString.GetArguments())
{
// do something to interpolation argument 'arg'
}
// use the standard interpolation and the current culture info
// to get an ordinary String:
var formatted = formattableString.ToString();
// ...
}
上記のメソッドを次のように呼び出します。
AddLogItem($"The foo is {foo}, and the bar is {bar}.");
例えば、ロギング・レベルがすでにログ項目を除外しようとしている場合、文字列をフォーマットする際のパフォーマンス・コストが発生しないように選択することができます。 暗黙のコンバージョン
補間された文字列からの暗黙的な型変換があります。
var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
また、不変の文脈で文字列を変換するためのIFormattable
変数を生成することもできます: var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";
現在および不変の文化方法
コード解析がオンの場合、補間された文字列はすべて警告CA1305 (Specify IFormatProvider
)を生成します。静的メソッドを使用して現在のカルチャを適用することができます。
public static class Culture
{
public static string Current(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.CurrentCulture);
}
public static string Invariant(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.InvariantCulture);
}
}
次に、現在のカルチャの正しい文字列を作成するには、次の式を使用します。
Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
注 :デフォルトでは、コンパイラは次のコードのコンパイルに失敗する補間された文字列式に String
型を割り当てているため、 Current
およびInvariant
を拡張メソッドとして作成することはできません。 $"interpolated {typeof(string).Name} string.".Current();
FormattableString
クラスにはすでにInvariant()
メソッドが含まれているため、不変のカルチャに切り替える最も簡単な方法は、 using static
をusing static
です。
using static System.FormattableString;
string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";
舞台裏
補間された文字列は、 String.Format()
単なる構文上の砂糖です。コンパイラ( Roslyn )はその背後でString.Format
します:
var text = $"Hello {name + lastName}";
上記は次のようなものに変換されます:
string text = string.Format("Hello {0}", new object[] {
name + lastName
});
文字列の補間とLinq
Linq文で補間された文字列を使用すると、読みやすくすることができます。
var fooBar = (from DataRow x in fooBarTable.Rows
select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();
次のように書き直すことができます:
var fooBar = (from DataRow x in fooBarTable.Rows
select $"{x["foo"]}{x["bar"]}").ToList();
再利用可能な補間文字列
string.Format
を使用すると、再利用可能な書式文字列を作成できます。
public const string ErrorFormat = "Exception caught:\r\n{0}";
// ...
Logger.Log(string.Format(ErrorFormat, ex));
ただし、補間された文字列は、存在しない変数を参照するプレースホルダとはコンパイルされません。以下はコンパイルされません:
public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context
代わりに、変数を消費し、 String
を返すFunc<>
を作成します。
public static Func<Exception, string> FormatError =
error => $"Exception caught:\r\n{error}";
// ...
Logger.Log(FormatError(ex));
文字列の補間とローカリゼーション
アプリケーションをローカライズする場合は、文字列補間とローカライゼーションを併用することが可能かどうか疑問に思うかもしれません。実際、リソースファイルにString
を格納する可能性を持つことは良いことです:
"My name is {name} {middlename} {surname}"
ずっと読みにくいのではなく、 "My name is {0} {1} {2}"
String
補間処理は、実行時に発生するstring.Format
で文字列を書式設定するのとは異なり、コンパイル時に発生します 。補間された文字列の式は、現在のコンテキストの名前を参照する必要があり、リソースファイルに格納する必要があります。つまり、ローカリゼーションを使用する場合は、次のようにする必要があります。
var FirstName = "John";
// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"),
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
// get localized string
var localizedMyNameIs = Properties.strings.Hello;
// insert spaces where necessary
name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
// display it
Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}
// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);
// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);
// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);
上記で使用された言語のリソース文字列が個々のリソースファイルに正しく格納されている場合、次の出力が得られます。
Bonjour、mon nom est
ハロー、私の名前istジョン
こんにちは、私の名前はジョンです
これは名前はすべての言語でローカライズされた文字列を、次のことを意味することに注意してください 。そうでない場合は、リソース文字列にプレースホルダを追加して上記の関数を変更するか、関数内のカルチャ情報を照会し、異なるケースを含むswitch case文を提供する必要があります。リソースファイルの詳細については、「 C#でのローカリゼーションの使い方」を参照してください。
翻訳が利用できない場合は、ほとんどの人が理解できるデフォルトのフォールバック言語を使用することをお勧めします。私はデフォルトのフォールバック言語として英語を使用することをお勧めします。
再帰的補間
それほど有用ではありませんが、他の中括弧の中に補間されたstring
再帰的に使用することは許されます:
Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");
出力:
文字列は27文字です:
私のクラスはMyClassと呼ばれています。
ついにキャッチを待つ
await
式を使用しawait
、C#6のcatch
およびfinally
ブロックのTasksまたはTask(Of TResult)にawait演算子を適用できます。
コンパイラの制限のために、以前のバージョンのcatch
およびfinally
ブロックでawait
式を使用することはできませんでした。 C#6は、 await
表現を可能にすることで、非同期タスクの待ち時間を大幅にawait
ます。
try
{
//since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
//since C#6
await logger.LogAsync(e);
}
finally
{
//since C#6
await service.CloseAsync();
}
C#5では、 bool
を使用するか、tryキャッチ以外のException
を非同期操作を実行するために宣言する必要がありました。このメソッドを次の例に示します。
bool error = false;
Exception ex = null;
try
{
// Since C#5
await service.InitializeAsync();
}
catch (Exception e)
{
// Declare bool or place exception inside variable
error = true;
ex = e;
}
// If you don't use the exception
if (error)
{
// Handle async task
}
// If want to use information from the exception
if (ex != null)
{
await logger.LogAsync(e);
}
// Close the service, since this isn't possible in the finally
await service.CloseAsync();
Null伝播
?.
演算子と?[...]
演算子は、 NULL条件演算子と呼ばれます 。また、 セーフ・ナビゲーション・オペレータなどの他の名前で呼ばれることもあります。
これは役に立ち.
(メンバアクセサ)演算子がnull
に評価される式に適用されると、プログラムはNullReferenceException
をスローします。開発者が代わりに?.
を使用する場合?.
(null-conditional)演算子の場合、式は例外をスローするのではなくヌルに評価されます。
もし?.
演算子が使用され、式はnullではあり?.
と.
同等です。
基本
var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null
classroom
教師がいない場合、 GetTeacher()
はnull
返すことがありnull
。 null
でName
プロパティにアクセスすると、 NullReferenceException
がスローされます。
?.
を使用するようにこのステートメントを変更した場合?.
構文では、式全体の結果はnull
になりnull
。
var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null
その後、 classroom
もnull
でもclassroom
ませんが、この文を次のように書くこともできnull
:
var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null
これは短絡の例です。ヌル条件付き演算子を使用する条件付きアクセス演算がNULLに評価されると、チェーン全体を処理せずに式全体がただちにNULLに評価されます。
ヌル条件付き演算子を含む式の終端メンバが値型の場合、式はその型のNullable<T>
に評価されるため、 ?.
除いた式の直接置換として使用することはできませ?.
。
bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime
bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed
bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null
bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable
NULL結合演算子で使用する(??)
null条件付き演算子とNULL合体演算子 ( ??
)を組み合わせて、式がnull
解決された場合にデフォルト値を戻すことができnull
。上記の例を使用して:
var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher()
// returns null OR classroom is null OR Name is null
インデクサーとの使用
ヌル条件付き演算子はインデクサで使用できます。
var firstStudentName = classroom?.Students?[0]?.Name;
上記の例では、
- 最初の
?.
classroom
がnull
でないことを保証しnull
。 - 第二
?
Students
コレクション全体がnull
でないことを保証しnull
。 - 三番目
?.
インデクサが[0]
インデクサがnull
オブジェクトを返さなかったことを保証した後。この操作でも引き続きIndexOutOfRangeException
がスローされることに注意してください。
voidと一緒に使う関数
ヌル条件付き演算子は、 void
関数とともに使用することもできます。ただし、この場合、ステートメントはnull
評価されません。これはNullReferenceException
防ぎます。
List<string> list = null;
list?.Add("hi"); // Does not evaluate to null
イベント呼び出しで使用
次のイベント定義を仮定します。
private event EventArgs OnCompleted;
伝統的にイベントを呼び出すときは、サブスクライバが存在しない場合にイベントがnull
かどうかをチェックすることがベストプラクティスです。
var handler = OnCompleted;
if (handler != null)
{
handler(EventArgs.Empty);
}
null条件付き演算子が導入されたため、呼び出しを1行に減らすことができます。
OnCompleted?.Invoke(EventArgs.Empty);
制限事項
ヌル条件付き演算子は左辺値ではなく右辺値を生成します。つまり、プロパティ割り当て、イベントサブスクリプションなどに使用できません。たとえば、次のコードは機能しません。
// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;
ゴッチャ
ご了承ください:
int? nameLength = person?.Name.Length; // safe if 'person' is null
同じではありません :
int? nameLength = (person?.Name).Length; // avoid this
前者が
int? nameLength = person != null ? (int?)person.Name.Length : null;
後者は:
int? nameLength = (person != null ? person.Name : null).Length;
三項演算子にもかかわらず、 ?:
が2つの場合の違いを説明するためにここで使用されていますが、これらの演算子は同等ではありません。これは次の例で簡単に説明できます。
void Main()
{
var foo = new Foo();
Console.WriteLine("Null propagation");
Console.WriteLine(foo.Bar?.Length);
Console.WriteLine("Ternary");
Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}
class Foo
{
public string Bar
{
get
{
Console.WriteLine("I was read");
return string.Empty;
}
}
}
どの出力:
Null伝播
私は読んだ
0
三元
私は読んだ
私は読んだ
0
複数の呼び出しを避けるには、次のようにします。
var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);
そしてこの違いは、ヌル伝播演算子がなぜ表現木でまだサポートされていないのかを幾分説明します。
静的型の使用
using static [Namespace.Type]
ディレクティブを使用すると、型と列挙値の静的メンバーをインポートできます。拡張メソッドは、トップレベルのスコープではなく、拡張メソッド(1つのタイプのみ)としてインポートされます。
using static System.Console;
using static System.ConsoleColor;
using static System.Math;
class Program
{
static void Main()
{
BackgroundColor = DarkBlue;
WriteLine(Sqrt(2));
}
}
using System;
class Program
{
static void Main()
{
Console.BackgroundColor = ConsoleColor.DarkBlue;
Console.WriteLine(Math.Sqrt(2));
}
}
過負荷解析の改善
次のスニペットは、デリゲートが期待されるときに(ラムダとは対照的に)メソッドグループを渡す例を示しています。オーバーロードの解決は、渡されたメソッドの戻り値の型をチェックするC#6の能力のためにあいまいなオーバーロードエラーを発生させる代わりに、これを解決します。
using System;
public class Program
{
public static void Main()
{
Overloaded(DoSomething);
}
static void Overloaded(Action action)
{
Console.WriteLine("overload with action called");
}
static void Overloaded(Func<int> function)
{
Console.WriteLine("overload with Func<int> called");
}
static int DoSomething()
{
Console.WriteLine(0);
return 0;
}
}
結果:
エラー
エラーCS0121: 'Program.Overloaded(System.Action)'および 'Program.Overloaded(System.Func)'のメソッドまたはプロパティ間で呼び出しが不明瞭です。
C#6では、 C#5でエラーが発生したラムダ式の完全一致の次のケースもうまく処理できます。
using System;
class Program
{
static void Foo(Func<Func<long>> func) {}
static void Foo(Func<Func<int>> func) {}
static void Main()
{
Foo(() => () => 7);
}
}
マイナーな変更とバグ修正
括弧は名前付きパラメータの周りでは禁止されています。 C#5ではコンパイルされますが、C#6ではコンパイルされません
Console.WriteLine((value: 23));
オペランドis
とas
、もはや法のグループであることを許可されません。 C#5ではコンパイルされますが、C#6ではコンパイルされません
var result = "".Any is byte;
ネイティブコンパイラはこれを許可しましたが(警告を表示しましたが)、実際には拡張メソッドの互換性もチェックしていなかったため、
1.Any is string
またはIDisposable.Dispose is object
です。
変更の更新については、 このリファレンスを参照してください。
コレクションの初期化に拡張メソッドを使用する
コレクションの初期化構文は、 IEnumerable
を実装するクラスをインスタンス化するときに使用でき、単一のパラメータを取るAdd
というメソッドがあります。
以前のバージョンでは、このAdd
メソッドは、初期化されるクラスのインスタンスメソッドでなければなりませんでした。 C#6では、拡張メソッドにすることもできます。
public class CollectionWithAdd : IEnumerable
{
public void Add<T>(T item)
{
Console.WriteLine("Item added with instance add method: " + item);
}
public IEnumerator GetEnumerator()
{
// Some implementation here
}
}
public class CollectionWithoutAdd : IEnumerable
{
public IEnumerator GetEnumerator()
{
// Some implementation here
}
}
public static class Extensions
{
public static void Add<T>(this CollectionWithoutAdd collection, T item)
{
Console.WriteLine("Item added with extension add method: " + item);
}
}
public class Program
{
public static void Main()
{
var collection1 = new CollectionWithAdd{1,2,3}; // Valid in all C# versions
var collection2 = new CollectionWithoutAdd{4,5,6}; // Valid only since C# 6
}
}
これは出力されます:
インスタンス追加メソッドで追加されたアイテム:1
インスタンス追加メソッドで追加されたアイテム:2
インスタンス追加メソッドで追加されたアイテム:3
エクステンションの追加方法を追加したアイテム:4
エクステンションの追加方法を追加したアイテム:5
拡張機能追加メソッド:6
警告を無効にする
C#5.0以前では、数だけ警告を抑制することができました。 Roslyn Analyzersの導入により、C#は特定のライブラリから発行された警告を無効にする方法を必要とします。 C#6.0では、プラグマディレクティブは名前で警告を抑制することができます。
前:
#pragma warning disable 0501
C#6.0:
#pragma warning disable CS0501