rksoftware

Visual Studio とか C# とかが好きです

C# 2.0 以降の新機能まとめ (C# 2.0 ~ 6.0)

 C# 2.0 以降の新機能の名前と公式ガイドページへのリンクをまとめました。(C# 2.0 ~ 6.0)

 注1)機能の名前はできるだけ公式ガイドから言葉を拾うようにしましたが、完全ではありません。
 注2)リンク先としてよいページを見つけられていないものもあります。
 注3)良さそうなページを探しましたが、もっと良いページがあるかもしれません。

■ C# 2.0 での新機能

・ジェネリック
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/
 List<T> クラスなどの T の機能。List<int> とすると int 型しか格納できない型安全な List を使用できる。

List<int> ints = new List<int>();
// ints.Add("text"); <- これはできない
ints.Add(1);
List<string> strings = new List<string>();
// strings.Add(1); <- これはできない
strings.Add("text");

・匿名メソッド
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-methods
 [推奨されません(※)] delegate をインライン コードで生成できる。 (※)C# 3.0 で追加されたラムダ式を使用してください。

System.Threading.Timer t = new System.Threading.Timer(
    delegate (object state) { Console.WriteLine("delegate"); } // 匿名メソッド
    , null, 0, 1000
    );
// "delegate" を繰り返し出力

・反復子 (yield)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/iterators
 反復子を使用して、リストや配列などのコレクションをステップ実行することができる。

static void Main(string[] args)
{
    foreach (int number in Values())
    {
        Console.WriteLine(number.ToString());
    }
    // 1
    // 2
    // 3
    // と出力
}

public static IEnumerable<int> Values()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

・部分型定義 (Partial クラス/構造体)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/partial-type
 クラス、構造体、またはインターフェイスの定義を複数のファイルに分割することができる。

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        a.A1(); // A1 を出力
        a.A2(); // A2 を出力
    }
}

partial class A
{
    public void A1()
    {
        Console.WriteLine("A1");
    }
}

partial class A
{
    public void A2()
    {
        Console.WriteLine("A2");
    }
}

・Null 許容型 (Nullable)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/nullable-types/
 Null 許容型は、基になる値型の値だけでなく、null 値も表すことができる。型名の後ろに ? をつけて変数宣言する。Value プロパティ等で値型で値を取り出せるが、多くの場所で Null 許容型のままでも期待する結果になる。

int? a = 1;
a = null;
Console.WriteLine(a == null);   // True
a = 1;
Console.WriteLine(a.Value);     // 1
Console.WriteLine(a);           // 1

・?? 演算子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/null-conditional-operator
 null 合体演算子と呼ばれる。左側のオペランドが null 値でない場合には左側のオペランドを返し、null 値である場合には右側のオペランドを返す。

string a = "a";
string b = null;
Console.WriteLine(a ?? "a is null");    // a
Console.WriteLine(b ?? "b is null");    // b is null

・プロパティの get/set 個別のアクセスレベル
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/restricting-accessor-accessibility
 プロパティの get アクセサーと set で異なるアクセス レベルを設定できる。

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        // a.PropertyA = 1; <- set は private なのでこれはできない
        a.SetPropertyA(10);
        Console.WriteLine(a.PropertyA);   // 10
    }
}

class A
{
    public int PropertyA { get; private set; }
    public void SetPropertyA(int value) { PropertyA = value; }
}

・static クラス
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members
 new キーワードを使用して、そのクラス型の変数を作成できないクラスを定義できる。class の前に static をつける。

class Program
{
    static void Main(string[] args)
    {
        // A a = new A(); <- これはできない
        A.PropertyA = 1;
        Console.WriteLine(A.PropertyA);   // 1
    }
}

static class A
{
    public static int PropertyA { get; set; }
}

・:: 演算子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/namespace-alias-qualifer
  名前を検索する名前空間エイリアスを指定する。global キーワードを指定すると、常にグローバル名前空間で検索が実行される。

using A = B;

class Program
{
    static void Main(string[] args)
    {
        // A.WriteLine();      // <- これはできない。A が class A なのか using A = B; なのかわからない
        // A.C.WriteLine();    // <- これはできない。A が class A なのか using A = B; なのかわからない

        global::A.WriteLine();  // A -- global 空間の A つまり class A
        A::C.WriteLine();       // C -- A 空間、つまり using A = B;
    }
}

class A
{
    internal static void WriteLine()
    {
        System.Console.WriteLine("A");
    }
}

namespace B
{
    class C
    {
        internal static void WriteLine()
        {
            System.Console.WriteLine("C");
        }
    }
}

・extern エイリアス
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/extern-alias
 同じ完全修飾型名を持つ、2 つアセンブリを参照する場合に名前空間のエイリアスを設定できる。  次の例では同じ名前の ClassLibrary.Class1.WriteLine() を持つ a.dll に A というエイリアスを、b.dll に B というエイリアスをつけるています。

手順

  • ソリューション エクスプローラーで実行プロジェクトの依存関係の中のクラスライブラリ プロジェクト (dll) のプロパティを開きます。
  • エイリアスにそれぞれ a.dll プロジェクトには A、b.dll プロジェクトには B と設定します。
  • 実行プロジェクト内でソースファイルの先頭に extern alias A; extern alias B; を追加します。これで A 名前空間エイリアスと B 名前空間エイリアスが使えるようになります。
  • :: 演算子を使用した名前空間エイリアスを指定でメソッドを呼びます。
extern alias A;
extern alias B;

class Program
{
    static void Main(string[] args)
    {
        A::ClassLibrary.Class1.WriteLine();    // a.dll のメソッドが呼ばれる
        B::ClassLibrary.Class1.WriteLine();    // b.dll のメソッドが呼ばれる
    }
}

a.dll

namespace ClassLibrary
{
    public class Class1
    {
        public static void WriteLine()
        {
            Console.WriteLine("A");
        }
    }
}

b.dll

namespace ClassLibrary
{
    public class Class1
    {
        public static void WriteLine()
        {
            Console.WriteLine("B");
        }
    }
}

・#pragma プリプロセッサディレクティブ
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-pragma
 特殊な命令をコンパイラに指示できる。
 #pragma warning disable 警告の番号 で指定した番号の警告が表示されないようにできる。#pragma warning restore 警告の番号 で警告が出るようになる。警告の番号は複数指定することも省略することも可能。省略した場合はすべての警告が非表示/表示になる。

#pragma warning disable CS0168
int i;  // CS0168 の警告(変数未使用)がでない
#pragma warning restore CS0168
int j;  // CS0168 の警告(変数未使用)がでる

・Conditional 属性
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/attributes/general#conditional-attribute
 条件付きコンパイル シンボルによる条件付きメソッドを定義ができる。シンボルが定義されていない場合、メソッドの呼び出しが行われない。
 条件付きコンパイル シンボルについての詳細は次のページがわかりやすそうです。
 ConditionalAttribute クラス (System.Diagnostics) | Microsoft Docs

 次の例では、プロジェクトのプロパティ > ビルド > DEBUG 定数の定義ON にしているかデバッグ実行の際にのみ Debug Mode が出力されます。

static void Main(string[] args)
{
    WriteDebug();   // デバッグ時のみ実行される
}

[System.Diagnostics.Conditional("DEBUG")]
public static void WriteDebug()
{
    Console.WriteLine("Debug Mode");
}

・固定サイズ バッファー (fixed)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers
 DLL や COM を扱う際に使える、固定サイズの配列を持ったバッファーを作成することができる。

public fixed char fixedBuffer[128];

・デリゲートの分散 (共変性と反変性)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates
 共変性により、メソッドの戻り値の型をデリゲートに定義されている型のサブクラスにできる。 反変性によりメソッドのパラメーター型をデリゲートのパラメーターの型のスーパークラスにできる。

class Program
{
    class A { }
    class A2 : A { }
    class A22 : A2 { }
    class A3 : A { }

    delegate A2 MyDelegate(A2 a2);

    static void Main(string[] args)
    {
        // これはできる。
        // A 型の引数に A2 型を渡しても OK。
        // A2 型の戻り値の実体が A22 型でも OK。
        MyDelegate deleg1 = Method1;    
        A a = deleg1(new A2());

        //MyDelegate deleg2 = Method2;    <- これはできない。A3 型の引数に A2 型は渡せない。
        //MyDelegate deleg3 = Method3;    < -これはできない。A2 型の戻り値の実体は A 型にできない。
    }

    static A22 Method1(A a) { return null; }

    static A2 Method2(A3 a) { return null; }

    static A Method3(A2 a) { return null; }
}

■ C# 3.0 での新機能

・型推論 (var)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/var
 変数の宣言時に暗黙の型指定を行える。コンパイラが型を推論してくれる。

var i = 10;      // i は int 型の変数
i = 20;          // <- i は int 型なのでこれはできる
// i = "text";   // i は int 型なのでこれはできない

・配列の型推論 (暗黙的に型指定される配列)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/arrays/implicitly-typed-arrays
 配列の宣言時に暗黙の型指定を行える。コンパイラが型を推論してくれる。

var ints = new[] { 0, 20, 30 }; // ints は int 型の配列。値から型を推論してくれる。
ints[0] = 10;                   // <- ints は int 型の配列なので、これはできる
// ints[0] = "text"; <- ints は int 型の配列なので、これはできない
foreach(var i in ints)
{
    Console.WriteLine(i); 
}
// 10 (ints[0] = 10; が実行されているので)
// 20 (初期値である 20)
// 30 (初期値である 30)

・自動実装プロパティ
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties
 値の取得または設定時にロジックが必要ない場合、プロパティを簡潔に宣言できる。

class Program
{
    static void Main(string[] args)
    {
        Phone p = new Phone();
        p.Name = "窓スマ";
        p.Size = 5.0;
        Console.WriteLine(string.Format("{0}, {1} インチ",p.Name, p.Size));    // 窓スマ, 5 インチ
    }
}

class Phone
{
    internal string Name { get; set; }
    internal double Size { get; set; }
}

・拡張メソッド
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/extension-methods
 既存の型に外からメソッドを追加できる(実際に追加されるわけではなく、追加されたかのようにコードを書ける)。
 次の例では、string クラスに HasValue メソッドが追加されたかのようにコードが書けている。

class Program
{
    static void Main(string[] args)
    {
        string s = "text";
        Console.WriteLine(s.HasValue());    // True
        s = null;
        Console.WriteLine(s.HasValue());    // False
    }
}

static class StringExtensions
{
    internal static bool HasValue(this string value)
    {
        return !string.IsNullOrEmpty(value);
    }
}

・ラムダ式
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions
 匿名関数。引数として渡したり関数呼び出しの結果値として返すことができるローカル関数を記述できる。あらゆる場所で使われるが、LINQ を使用する際に特に重要。

System.Threading.Timer t = new System.Threading.Timer(
    state => Console.WriteLine("lambda") // ラムダ式による匿名メソッド。引数 state を取り返り値を持たない関数が定義されている
    // (object state) => { Console.WriteLine("lambda"); } // <- 引数の型名を明示するこのような記述も可能
    ,null, 0, 1000
);
// "lambda" を繰り返し出力

・LINQ
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/linq/query-syntax-and-method-syntax-in-linq
 データソースからデータを取得する。その際、変換や集計、グループ化などを行うことも可能。クエリ式とメソッド式という 2 つの形式があるが、一般にメソッド式が使われる。下記はメソッド式の例。

int[] data = new[] { 1, 2, 3, 4, 5, 6 };

var evens = data.Where(x => x % 2 == 0);        // 偶数だけ取り出す
foreach (var i in evens)
{
    Console.Write(i);  // 246
}

var ws = data.Select(x => x * 2);               // 全てに 2 を乗算する
foreach (var i in ws)
{
    Console.Write(i);     // 24681012
}

var evensws = data.Where(x => x % 2 == 0).Select(x => x * 2);   // 偶数だけ取り出し 2 を乗算する
foreach (var i in evensws)
{
    Console.Write(i);     // 4812
}

Console.WriteLine(data.Max());  // 6;
Console.WriteLine(data.Min());  // 1;
Console.WriteLine(data.Sum());  // 21

・匿名型
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types
 明示的に型を定義することなく、複数の読み取り専用プロパティを持ったオブジェクトを生成できる。 各プロパティの型はコンパイラにより推論される。

var p = new { Name = "窓スマ", Size = 5.0 };

// 読み取り専用のプロパティを持っている
Console.WriteLine(string.Format("{0}, {1} インチ", p.Name, p.Size));      // 窓スマ, 5 インチ
// 型は匿名の型
Console.WriteLine(string.Format("p is {0}", p.GetType().Name));          // p is <>f__AnonymousType0`2
// プロパティの型は推論される
Console.WriteLine(string.Format("Name is {0}", p.Name.GetType().Name));  // Name is String
Console.WriteLine(string.Format("Size is {0}", p.Size.GetType().Name));  // Name is Double

・オブジェクト初期化子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers
 オブジェクトの作成時にプロパティに、値を割り当てることができる。匿名型の使用時にも使われる。

class Program
{
    static void Main(string[] args)
    {
        // X を 10、Y を 20 で初期化
        MyPoint p = new MyPoint() { X = 10, Y = 20 };
        Console.WriteLine(string.Format("{0}, {1}", p.X, p.Y)); // 10, 20

        // 匿名型一つ目のインスタンスの Source プロパティが 1、W プロパティが 2 で初期化
        // 匿名型二つ目のインスタンスの Source プロパティが 2、W プロパティが 4 で初期化
        // 匿名型三つ目のインスタンスの Source プロパティが 3、W プロパティが 6 で初期化
        var anonymous = new[] { 1, 2, 3 }.Select(x => new { Source = x, W = x * 2 });
        foreach(var m in anonymous)
        {
            Console.WriteLine(string.Format("{0}, {1}", m.Source, m.W)); // 1, 2
                                                                         // 2, 4
                                                                         // 3, 6
        }
    }
}

class MyPoint
{
    internal int X { get; set; }
    internal int Y { get; set; }
}

・Partial メソッド
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/partial-method
 Partial クラスの定義時にメソッドのシグネチャと実装を分けられる。定義できるメソッドは void を返す private メソッドのみ。

class Program
{
    static void Main(string[] args)
    {
        new A().Method();           // 実装がないので何も出力されない
        Console.WriteLine("====="); // =====
        new B().Method();           // Class B Method
    }
}

// クラス A の PartialMethod メソッドのシグネチャの定義
partial class A
{
    internal void Method()
    {
        this.PartialMethod("Class A Method");
    }

    // メソッドのシグネチャの定義 実装はここにはない
    partial void PartialMethod(string text);
}

// クラス A の PartialMethod メソッドのシグネチャの実装
partial class A
{
    // メソッドのシグネチャの実装 これは実装のないパターン
    //partial void PartialMethod(string text)
    //{
    //    Console.WriteLine(text);
    //}
}

// クラス B の PartialMethod メソッドのシグネチャの定義
partial class B
{
    internal void Method()
    {
        this.PartialMethod("Class B Method");
    }

    // メソッドのシグネチャの定義 実装はここにはない
    partial void PartialMethod(string text);
}

// クラス B の PartialMethod メソッドのシグネチャの実装
partial class B
{
    // メソッドのシグネチャの実装 これは実装のあるパターン
    partial void PartialMethod(string text)
    {
        Console.WriteLine(text);
    }
}

■ C# 4.0 での新機能

・dynamic 型
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/reference-types#the-dynamic-type
 dynamic 型はコンパイル時の型チェックを行わず、実行時に解決される。

try
{
    // 中身は int
    dynamic a = 1;
    Console.WriteLine(a);                   // 1
    Console.WriteLine(a.GetType().Name);    // Int32

    // int なので足し算ができる
    a = a + 1;
    Console.WriteLine(a);                   // 2

    // 文字列を入れることもできる 足し算も文字列の足し算になる
    a = "text";
    a = a + 1;
    Console.WriteLine(a);                   // text1
    Console.WriteLine(a.GetType().Name);    // String
    Console.WriteLine(a.ToString());        // text1

    // 実際にはないメソッドも書けるが実行時エラー
    Console.WriteLine(a.ToString2());       // <- 実行時エラー「'string' に 'ToString2' の定義がありません」
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

・省略可能な引数 (オプション引数)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments
 メソッドなどのパラメーターに規定値を設定し、省略可能にできる。

static void Main(string[] args)
{
    // 引数を省略しないパターン
    AddAndWriteLine(10, 20, "計算結果 => {0}"); // 計算結果 => 30

    // 引数 format を省略したパターン
    AddAndWriteLine(10, 20);                   // 結果は 30 です.

    // 引数 b、format を省略したパターン
    AddAndWriteLine(1);                        // 結果は 3 です.
}

// 引数 b と、format は初期値が設定されているので省略が可能
static int AddAndWriteLine(int a, int b=2, string format="結果は {0} です.")
{
    var c = a + b;
    Console.WriteLine(string.Format(format, c));
    return c;
}

・名前付き引数
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments
 メソッドなどを呼び出す際、パラメーターを名前付きで記述できる。名前付きで記述した場合、パラメーターを任意の順に指定することも可能。

static void Main(string[] args)
{
    AddAndWriteLine(10, 20, "計算結果 => {0}");             // 計算結果 => 30
    // AddAndWriteLine(10, "計算結果 => {0}", 20);          // <- これはできない(名前付き引数でない場合はパラメーターの順番を変えられない)
    AddAndWriteLine(10, format: "計算結果 => {0}", b: 20);  // <- パラメーターを任意の順序で指定できる
    AddAndWriteLine(10, format: "計算結果 => {0}");         // <- 途中のパラメーターを省略できる
    AddAndWriteLine(10, 20, format: "計算結果 => {0}");     // 引数の意味が分かりやすくなる

static int AddAndWriteLine(int a, int b = 2, string format = "結果は {0} です.")
{
    var c = a + b;
    Console.WriteLine(string.Format(format, c));
    return c;
}

・ジェネリックの共変性と反変性
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/in-generic-modifier
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/out-generic-modifier
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/covariance-contravariance/variance-in-generic-interfaces
 共変性により、ジェネリック型の変数に型パラメータがサブクラスにであるオブジェクトを代入できる。 反変性によりジェネリック型の変数に型パラメータがスーパークラスにであるオブジェクトを代入できる。

class Program
{
    static void Main(string[] args)
    {
        // 反変 実体のメソッド Set(object) に、Set(string) で string をパラメーターとしても問題ない
        IA<string> a = new A<object>();
        a.Set("text");

        // 共変 実体の戻り値 string を、object 変数に代入しても問題ない
        IB<object> b = new B<string>();
        object o = b.Get();
    }
}

// in キーワードが反変のしるし
interface IA<in T>
{
    void Set(T t);
}

class A<T> : IA<T>
{
    public void Set(T t) { ; }
}

// out キーワードが共変のしるし
interface IB<out T>
{
    T Get();
}

class B<T> : IB<T>
{
    private T _t;
    public void Set(T t) { _t = t; }
    public T Get() { return _t; }
}

■ C# 5.0 での新機能

・非同期処理 (async/await)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/async/
 同期メソッドの作成とほぼ同様の容易さで、非同期メソッドを作成できる。async が付いたメソッドは非同期メソッドになる。非同期メソッドの完了を待機する場合は await をつけてメソッドを呼び出す。

static void Main(string[] args)
{
    Method();               // 同期メソッドでの取得
    MethodAsync().Wait();   // 非同期メソッドでの取得

    Console.ReadKey();
}

// 非同期メソッド (async がついている)
static async Task<string> MethodAsync()
{
    var client = new System.Net.Http.HttpClient();
    // GetStringAsync は非同期メソッドなので await で待機できる
    var html = await client.GetStringAsync("http://rksoftware.hatenablog.com/");
    Console.WriteLine(html.Take(100).ToArray());
    return html;
}

// 同期メソッド
static void Method()
{
    var client = new System.Net.WebClient();
    // データの取得を非同期に行うために、コールバックを登録するコード
    client.DownloadStringCompleted +=
        (object sender, System.Net.DownloadStringCompletedEventArgs e) =>
        {
            Console.WriteLine(new string(e.Result.Take(100).ToArray()));
        };
    client.DownloadStringAsync(new Uri("http://rksoftware.hatenablog.com/"));
}

・呼び出し元情報の属性 (CallerMemberName など)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/caller-information
 メソッドの呼び出し元の情報を省略可能な引数として取得できる。メソッドを呼び出す際に引数を省略すると、呼び出し元のメソッド名などが設定される。
 つまり、呼び出されたメソッド側で呼び出したメソッドの情報を知ることができる。

static void Main(string[] args)
{
    MyMethod();         // message: message
                        // member name: MyMethod
                        // source file path: XXXXX\XXXXX\Program.cs
                        // source line number: 21
    MyProperty = 10;    // MyProperty changed: 0-> 10
}

// このメソッドの引数についている属性
// これらの属性のついた引数を省略してメソッドを呼び出す
static void WriteTrace(string message,
        [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
        [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
        [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
    Console.WriteLine(string.Format("message: {0}", message));
    Console.WriteLine(string.Format("member name: {0}", memberName));
    Console.WriteLine(string.Format("source file path: {0}", sourceFilePath));
    Console.WriteLine(string.Format("source line number: {0}", sourceLineNumber));
}

// これが頻繁に使う属性
// ここではプロパティの set から呼ばれていて、変更されたプロパティ名を出力することに使っている
static void WritePropertyChanged(object oldValue,
    object newValue,
    [System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
    Console.WriteLine(string.Format("{0} changed: {1} -> {2}", name, oldValue, newValue));
}

static void MyMethod()
{
    WriteTrace("message");
}

private static int _myProperty;
static int MyProperty {
    get { return _myProperty; }
    set { WritePropertyChanged(_myProperty, value); _myProperty = value; }
}

■ C# 6.0 での新機能

・文字列補間
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/interpolated-strings
 含まれる挿入式をその文字列表現に置き換えた文字列を返す。

string firstName = "埼玉";
string lastName = "太郎";
Console.WriteLine($"{firstName} {lastName}");   // 埼玉 太郎

・自動実装プロパティ get アクセサーのみの宣言
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties
  get アクセサーのみを宣言し、変更できないプロパティの作成できる。

class Program
{
    static void Main(string[] args)
    {
        MyClass c = new MyClass();
        int i = c.MyProperty;   // <- これはできる (get は宣言している)
        // c.MyProperty = 20; <- これはできない (set は宣言していない)
    }
}

class MyClass
{
    internal int MyProperty { get; }
    public MyClass()
    {
        MyProperty = 10;    // コンストラクター内では初期化できる
    }
}

・自動実装プロパティの初期化
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties
 フィールドと同様に自動実装プロパティを初期化することができる。

static void Main(string[] args)
{
    int i = MyProperty;
    Console.WriteLine(i);   // 10
}

// 宣言時に初期化している
static int MyProperty { get; set; } = 10;

・式形式のメンバー
 https://docs.microsoft.com/ja-jp/dotnet/csharp/methods#expr
 メソッドの本文が 1 つのステートメントの場合、=> を使用した構文ショートカットが使用できる。

static void Main(string[] args)
{
    MyClass c = new MyClass();
    c.FirstName = "埼玉";
    c.LastName = "太郎";
    Console.WriteLine(c.ToString());   // 埼玉 太郎

    Console.ReadKey();
}

class MyClass
{
    internal string FirstName { get; set; }
    internal string LastName { get; set; }
    public override string ToString() => $"{FirstName} {LastName}";
}

・get アクセサーのみの自動実装プロパティ を式本体の定義
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/properties
 読み取り専用プロパティの場合に get アクセサーを式形式のメンバーとして実装できる。

static void Main(string[] args)
{
    FirstName = "埼玉";
    LastName = "太郎";
    Console.WriteLine(FullName);   // 埼玉 太郎

}

static string FirstName { get; set; }
static string LastName { get; set; }

// get のみのプロパティを式形式のメンバーで宣言している
static string FullName => $"{FirstName} {LastName}";

・using static ディレクティブ
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/using-static
 static メソッドなどへアクセスする際、名前空間と同様に型名も指定せずに使用できる using の記述ができる。

using System;
using System.Threading.Tasks;
using static System.Math;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Math.PI); // 3.14159265358979
            Console.WriteLine(PI);      // 3.14159265358979
        }
    }
}

・Null 条件演算子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/null-conditional-operators
 メンバー アクセスおよびインデックス操作のチェーンの 1 つの演算が null を返す場合、チェーンの実行の残りの部分を停止し null を返すショートサーキット。

string s = "text";

// s が null でないので null 条件演算子を使っていなくても例外は発生しない
Console.WriteLine(s.Length.ToString()); // 4
Console.WriteLine(s.ToCharArray()[0]); // t

s = null;
try
{
    Console.WriteLine(s.Length.ToString()); // s が null なので例外発生
    Console.WriteLine(s.ToCharArray()[0]);
}
catch (NullReferenceException ex)
{
    Console.WriteLine(ex.Message);
}

// s が null なのでプロパティやメソッドは実行せず null を返す
Console.WriteLine(s?.Length.ToString() == null); // True
Console.WriteLine(s?.ToCharArray()?[0] == null);  // True

・nameof 式
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/nameof
 変数、型、またはメンバーの名前を文字列として取得できる。

class Program
{
    static void Main(string[] args)
    {
        int variable;

        Console.WriteLine(nameof(Program));     // Program
        Console.WriteLine(nameof(variable));    // variable
        Console.WriteLine(nameof(MyMethod));    // MyMethod
        Console.WriteLine(nameof(MyProperty));  // MyProperty
    }

    static void MyMethod() { ; }
    static int MyProperty { get; set; }
}

・例外をフィルター処理する述語式 (when)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/try-catch
 処理対象の例外をフィルターする際に、例外オブジェクトの型の評価に続いて、述語式を使用して例外をフィルターできる。

static void Main(string[] args)
{
    MethodA(null, "text");  // text1:Null または Empty です (Parameter 'text1')
    MethodA("text", null);  // text1:Null または Empty です (Parameter 'text2')
}
static void MethodA(string text1, string text2)
{
    try
    {
        MethodB(text1, text2);
    }
    // 例外の型のみでなくプロパティの内容も評価してフィルター ex.ParamName == "text1" の場合のみ
    catch (ArgumentException ex) when (ex.ParamName == "text1")
    {
        Console.WriteLine($"{nameof(text1)}:{ex.Message}");
    }
    // 例外の型のみでなくプロパティの内容も評価してフィルター ex.ParamName == "text2" の場合のみ
    catch (ArgumentException ex) when (ex.ParamName == "text2")
    {
        Console.WriteLine($"{nameof(text2)}:{ex.Message}");
    }
}

static void MethodB(string text1, string text2)
{
    if (string.IsNullOrEmpty(text1)) throw new ArgumentException("Null または Empty です", nameof(text1));
    if (string.IsNullOrEmpty(text2)) throw new ArgumentException("Null または Empty です", nameof(text2));
}

・catch/finally ブロック内での await
 該当のドキュメントが見つかりませんでした。
 catch および finally ブロックで await による非同期処理ができる。

static void Main(string[] args)
{
    MethodAsync().Wait();   // 非同期メソッドでの取得
}

static async Task<string> MethodAsync()
{
    var client = new System.Net.Http.HttpClient();
    var html = await client.GetStringAsync("http://rksoftware.hatenablog.com/about");
    try
    {
        html = html.Substring(0, int.MaxValue);
    }
    catch(ArgumentOutOfRangeException ex)
    {
        Console.WriteLine(ex.Message);
        // catch の中で await できる
        html = await client.GetStringAsync("http://rksoftware.hatenablog.com/about");
        html = new string(html.Take(100).ToArray());
    }

    Console.WriteLine(html);

    return html;
}

・インデックス初期化 (Dictionary 初期化)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers
 単純なコレクションだけでなく、コレクションがインデックスでの値の読み書きができる場合、インデックス指定で初期化できる。

List<string> list = new List<string>
{
    "saitama",
    "gunma",
    "ibaraki"
};

Dictionary<int, string> dictionary = new Dictionary<int, string>
{
    [101] = "saitama",
    [201] = "gunma",
    [301] = "ibaraki"
};

Console.WriteLine(list[0]);         // saitama
Console.WriteLine(dictionary[201]); // gunma

・拡張メソッドでコレクション初期化子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
 Add メソッドが拡張メソッドで定義されている型も、初期化子を指定できる。

class Program
{
    static void Main(string[] args)
    {
        MyClass c = new MyClass() { "saitama", "gunma", "ibaraki" };
        Console.WriteLine(c);   // saitama, gunma, ibaraki
    }
}

// Add 拡張メソッド
static class MyClassExtension
{
    internal static void Add(this MyClass c, string text)
    {
        c.Push(text);
    }
}

// Add 拡張メソッドをつけるのは IEnumerable<T> を実装する必要があるため、実装クラスの宣言
class MyClass: IEnumerable<string>
{
    private List<string> _texts = new List<string>();
    internal void Push(string text) { _texts.Add(text); }
    public override string ToString() { return string.Join(", ", _texts); }
    public IEnumerator<string> GetEnumerator() { return _texts.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return _texts.GetEnumerator(); }
}

・オーバーロードの解決の改善
  [ 良いリンクを見つけられませんでした ]
 オーバーロードで引数に Action を受け取るメソッドと Func を受け取るメソッドがある場合のコンパイラによる選択が改善した。

static void Main(string[] args)
{
    Task.Run(() => DoMethod()); // 以前はこう書く必要があった
    Task.Run(DoMethod);         // 今はコンパイラがかしこくなってこう書ける
}

static Task DoMethod()
{
    Console.WriteLine("DoMethod");
    return Task.FromResult(0);
}

■ 出典/参考文献

 上記まとめの作成に、次のサイトを大いに参考にさせていただきました。
 文言やサンプルコードは C# のガイド のコードを利用させていただきました。

C# のガイド | Microsoft Docs

C# によるプログラミング入門 | ++C++; // 未確認飛行 C