10分で分かる C# 7.0 の新機能

[What's New in C# 7](https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7)、[New Features in C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/) をベースに、C# 6 と比較してコードがどう変わるのかを記載しています。


## 1. [`out` 変数 (`out` variables)](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/out-parameter-modifier#calling-a-method-with-an-out-argument)
```csharp:old
int number;
if (int.TryParse(input, out number)) {
    Console.WriteLine(number);
}
```
<div>:arrow_down:</div>
```csharp:new!
if (int.TryParse(input, out var number)) {
    Console.WriteLine(number);
}
```

## 2. [パターンマッチング (Pattern matching)](https://docs.microsoft.com/ja-jp/dotnet/csharp/pattern-matching)
### `is` 式でのパターン (`is` expressions with patterns)
```csharp:old
public static int Count<T>(this IEnumerable<T> source) {
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (source is ICollection<T>) {
        return ((ICollection<T>)source).Count;
    }
    if (source is ICollection) {
        return ((ICollection)source).Count;
    }
    ...
}
```
<div>:arrow_down:</div>
```csharp:new!
public static int Count<T>(this IEnumerable<T> source) {
    if (source is null) throw new ArgumentNullException(nameof(source));
    if (source is ICollection<T> genericCollection) {
        return genericCollection.Count;
    }
    if (source is ICollection collection) {
        return collection.Count;
    }
    ...
}
```
### `switch` 文でのパターン (`switch` statements with patterns)
```csharp:old
public static T FirstOrDefault<T>(this IEnumerable<T> source) {
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (source is IList<T>) {
        var list = (IList<T>)source;
        if (list.Count > 0) {
            return list[0];
        }
    }
    ...
}
```
<div>:arrow_down:</div>
```csharp:new!
public static T FirstOrDefault<T>(this IEnumerable<T> source) {
    switch (source) {
        case null: throw new ArgumentNullException(nameof(source));
        case IList<T> list when list.Count > 0: return list[0];
    }
    ...
}
```

## 3. [タプル (Tuples)](https://docs.microsoft.com/ja-jp/dotnet/csharp/tuples)
```csharp:old
var range = Range(numbers);
Console.WriteLine($"min:{range.Item1}, max:{range.Item2}");
...
public static Tuple<int, int> Range(IEnumerable<int> source) {
    int min = default(int);
    int max = default(int);
    ...
    return Tuple.Create(min, max);
}
```
<div>:arrow_down:</div>
```csharp:new!
var range = Range(numbers);
Console.WriteLine($"min:{range.Min}, max:{range.Max}");
...
public static (int Min, int Max) Range(IEnumerable<int> source) {
    int min = default(int);
    int max = default(int);
    ...
    return (min, max);
}
```

## 4. [分解 (Deconstruction)](https://docs.microsoft.com/ja-jp/dotnet/csharp/deconstruct)
```csharp:old
var point = new Point(1, 2);
var a = point.X;
var b = point.Y;
```
<div>:arrow_down:</div>
```csharp:new!
(var a, var b) = new Point(1, 2);
...
public static class PointExtensions {
    public static void Deconstruct(this Point point, out int x, out int y) {
        x = point.X;
        y = point.Y;
    }
}
```
`Deconstruct` メソッドを用意する事で、あらゆるオブジェクトを任意に分解可能となります。

## 5. [破棄 (Discards)](https://docs.microsoft.com/ja-jp/dotnet/csharp/discards)
```csharp:old
int workerThreads, tmp;
ThreadPool.GetAvailableThreads(out workerThreads, out tmp);
```
<div>:arrow_down:</div>
```csharp:new!
ThreadPool.GetAvailableThreads(out var workerThreads, out _);
```
不要な値の代入先に破棄変数 `_` を用いる事で、メモリ割り当てを減らし、コードの意図を明確にします。

## 6. [ローカル関数 (Local functions)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/local-functions)
```csharp:old
public static IEnumerable<int> OddSequence(int start, int end) {
    // 引数の評価処理も反復子に含まれている為、実際に反復処理が行われるまで評価が遅延されてしまう。
    if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));
    if (end <= start) throw new ArgumentException("start must be less than end.");
    
    for (int i = start; i <= end; i++) {
        if (i % 2 == 1) {
            yield return i;
        }
    }
}
```
<div>:arrow_down:</div>
```csharp:new!
public static IEnumerable<int> OddSequence(int start, int end) {
    // 反復子がローカル関数に隔離されているので、引数の評価は即時に行われる。
    if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));
    if (end <= start) throw new ArgumentException("start must be less than end.");
    
    return GetOddSequenceEnumerator();
    
    IEnumerable<int> GetOddSequenceEnumerator() {
        for (int i = start; i <= end; i++) {
            if (i % 2 == 1) {
                yield return i;
            }
        }
    }
}
```
ローカル関数によって、評価のタイミングの異なる処理を効果的に分割できます。

## 7. [数値リテラルの改善 (Numeric literal syntax improvements)](https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7#numeric-literal-syntax-improvements)
### バイナリリテラル (Binary Literals)
```csharp:old
var mask = 0x3b;
```
<div>:arrow_down:</div>
```csharp:new!
var mask  = 0b0011_1011;
```

### 桁区切り (Digit Separators)
```csharp:old
var d = 1000000;
var x = 0x504B0304;
```
<div>:arrow_down:</div>
```csharp:new!
var d = 1_000_000;
var x = 0x50_4B_03_04;
```

## 8. [`ref` 戻り値と `ref` ローカル変数 (`ref` returns and locals)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/ref-returns)
```csharp:old
void Convert(System.Drawing.Point[] points) {
    for(var i = 0; i < points.Length; i++) {
        var p = points[i];
        p.X += 10;
        p.Y += 20;
        points[i] = p;
    }
}
```
<div>:arrow_down:</div>
```csharp:new!
void Convert(System.Drawing.Point[] points) {
    for(var i = 0; i < points.Length; i++) {
        ref var p = ref points[i];
        p.X += 10;
        p.Y += 20;
    }
}
```

## 9. [一般化された async の戻り値の型と ValueTask (Generalized async return types and ValueTask)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/async/async-return-types#generalized-async-return-types-and-valuetaskt)
```csharp:old
private int? _cached;

public async Task<int> GetValueAsync() {
    if (_cached == null) {
        _cached = await Task.Run(() => 123);
    }
    return _cached.Value;
}
```
<div>:arrow_down:</div>
```csharp:new!
private int? _cached;

public async ValueTask<int> GetValueAsync() {
    if (_cached == null) {
        _cached = await Task.Run(() => 123);
    }
    return _cached.Value;
}
```
戻り値が値型になった事で、参照型（`Task`）のインスタンス化による追加のメモリ割り当てを回避する事が可能となりました。

## 10. [式形式のメンバーの拡充 (More expression-bodied members)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
```csharp:old
class Person {
    private string _name;
    
    // コンストラクター
    public Person(string name) {
        _name = name;
    }
    
    // ファイナライザー
    ~Person() {
        Console.Error.WriteLine("Finalized");
    }
    
    // プロパティ
    public string Name {
        get { return _name; }
        set { _name = value; }
    }
}
```
<div>:arrow_down:</div>
```csharp:new!
class Person {
    private string _name;
    
    // コンストラクター
    public Person(string name) => _name = name;
    
    // ファイナライザー
    ~Person() => Console.Error.WriteLine("Finalized");
    
    // プロパティ
    public string Name {
        get => _name;
        set => _name = value;
    }
}
```

## 11. [`throw` 式 (`throw` Expressions)](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/throw#the-throw-expression)
```csharp:old
class Person {
    public string Name { get; }

    public Person(string name) {
        if (name == null) throw new ArgumentNullException(nameof(name));
        Name = name;
    }
    
    public string GetFirstName() {
        var parts = Name.Split(' ');
        if (parts.Length <= 0) {
            throw new InvalidOperationException("No name!");
        }
        return parts[0];
    }
    
    public string GetLastName() {
        throw new NotImplementedException();
    }
}
```
<div>:arrow_down:</div>
```csharp:new!
class Person {
    public string Name { get; }

    // null 合体演算子
    public Person(string name) {
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
    
    // 条件演算子
    public string GetFirstName() {
        var parts = Name.Split(' ');
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }
    
    // 式形式のラムダ
    public string GetLastName() => throw new NotImplementedException();
}
```


## For more information
- [What's New in C# 7 - C# Guide | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7)
- [New Features in C# 7.0 | .NET Blog](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/)