サーチ…


前書き

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を使用することで同様の機能を実現できます。

6.0

式:

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 };
}

制限事項

エクスプレッションボディメンバーにはいくつかの制限があります。ブロックステートメントやブロックを含むステートメントを含むことはできません: ifswitchforforeachwhiledotryなど

いくつかの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. }

この例aaは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#での一般的なアプローチは、例外のログと再スローでした。

6.0
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クラスは、プロパティを初期化するために使用できるオプションを示しています。

6.0
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";

アクセサもすることができinternalinternal protected 、またはprivate


読み取り専用のプロパティ

可視性の柔軟性に加えて、読み取り専用の自動プロパティを初期化することもできます。ここに例があります:

    public List<string> Ingredients { get; } = 
        new List<string> { "dough", "sauce", "cheese" };

この例では、複合型のプロパティを初期化する方法も示しています。また、自動プロパティは書き込み専用にすることもできないため、書き込み専用の初期化も排除されます。


古いスタイル(pre C#6.0)

C#6より前では、より冗長なコードが必要でした。プロパティのバッキングプロパティと呼ばれる1つの余分な変数を使用して、デフォルト値を与えたり、以下のようなpublicプロパティを初期化したりしました。

6.0
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変数foobarが作成され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です。

デモを見る


文字列補間では、中括弧{}内のも評価できます。結果は文字列内の対応する位置に挿入されます。たとえば、 foobar最大値を計算して挿入するには、中括弧内で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 staticusing 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返すことがありnullnullNameプロパティにアクセスすると、 NullReferenceExceptionがスローされます。

?.を使用するようにこのステートメントを変更した場合?.構文では、式全体の結果はnullになりnull

var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null

デモを見る

その後、 classroomnullでも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;

上記の例では、

  • 最初の?. classroomnullでないことを保証し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つのタイプのみ)としてインポートされます。

6.0
using static System.Console;
using static System.ConsoleColor;
using static System.Math;

class Program
{
    static void Main()
    {
        BackgroundColor = DarkBlue;
        WriteLine(Sqrt(2));
    }
}

ライブデモフィドル

6.0
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;
    }
}

結果:

6.0

出力

Func <int>を使ったオーバーロードが呼び出されました

デモを見る

5.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ではコンパイルされません

5.0
Console.WriteLine((value: 23));

オペランドisas 、もはや法のグループであることを許可されません。 C#5ではコンパイルされますが、C#6ではコンパイルされません

5.0
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


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow