C# Language
C#7.0の機能
サーチ…
前書き
C#7.0はC#の7番目のバージョンです。このバージョンには、タプルの言語サポート、ローカル関数、 out var
宣言、数字区切り文字、バイナリリテラル、パターンマッチング、スロー式、 ref return
およびref local
および拡張式ボディメンバーリストなどの新機能が含まれています。
公式リファレンス: C#7の新機能
アウトvar宣言
C#の一般的なパターンはbool TryParse(object input, out object value)
を安全に解析するためにbool TryParse(object input, out object value)
を使用しています。
out var
宣言は、読みやすくするための簡単な機能です。変数をoutパラメータとして渡すと同時に宣言することができます。
このように宣言された変数は、それが宣言された時点でボディの残りの部分にスコープされます。
例
C#7.0より前のTryParse
を使用する場合は、関数を呼び出す前に値を受け取る変数を宣言する必要があります。
int value;
if (int.TryParse(input, out value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // ok
C#7.0では、 out
パラメータに渡される変数の宣言をインライン展開することができ、別の変数宣言の必要はありout
。
if (int.TryParse(input, out var value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // still ok, the value in scope within the remainder of the body
関数が返すにパラメータの一部ならばout
必要とされていないあなたは、 廃棄演算子を使用することができます_
。
p.GetCoordinates(out var x, out _); // I only care about x
out var
宣言は、既に持っている既存の機能を使用することができますout
のパラメータを。関数宣言の構文は変わりません。また、関数をout var
宣言と互換性を持たせるための追加の要件は必要ありません。この機能は単純に構文的な砂糖です。
out var
宣言のもう一つの特徴は、匿名型で使用できることです。
var a = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var groupedByMod2 = a.Select(x => new
{
Source = x,
Mod2 = x % 2
})
.GroupBy(x => x.Mod2)
.ToDictionary(g => g.Key, g => g.ToArray());
if (groupedByMod2.TryGetValue(1, out var oddElements))
{
Console.WriteLine(oddElements.Length);
}
このコードでは、 int
型キーと匿名型の配列を持つDictionary
を作成します。以前のバージョンのC#では、 out
変数(これは匿名型です)を宣言する必要があったため、 TryGetValue
メソッドを使用することは不可能でした。しかし、でout var
我々は明示のタイプを指定する必要はありませんout
変数を。
制限事項
式がラムダ式の式として解釈されるので、var宣言はLINQクエリでの使用が制限されているため、導入される変数の範囲はこれらのラムダに限定されます。たとえば、次のコードは機能しません。
var nums =
from item in seq
let success = int.TryParse(item, out var tmp)
select success ? tmp : 0; // Error: The name 'tmp' does not exist in the current context
参考文献
バイナリリテラル
0b接頭辞は、バイナリリテラルを表すために使用できます。
バイナリリテラルでは、ゼロと1からの数値を作成することができます。これにより、数値のバイナリ表現にどのビットが設定されているかがわかりやすくなります。これは、バイナリフラグを扱う場合に便利です。
以下は、値34
(= 2 5 + 2 1 )のint
を指定する同等の方法です。
// Using a binary literal:
// bits: 76543210
int a1 = 0b00100010; // binary: explicitly specify bits
// Existing methods:
int a2 = 0x22; // hexadecimal: every digit corresponds to 4 bits
int a3 = 34; // decimal: hard to visualise which bits are set
int a4 = (1 << 5) | (1 << 1); // bitwise arithmetic: combining non-zero bits
フラグ列挙型
これまで、 enum
フラグ値を指定するには、この例の3つのメソッドのいずれかを使用する必要がありました。
[Flags]
public enum DaysOfWeek
{
// Previously available methods:
// decimal hex bit shifting
Monday = 1, // = 0x01 = 1 << 0
Tuesday = 2, // = 0x02 = 1 << 1
Wednesday = 4, // = 0x04 = 1 << 2
Thursday = 8, // = 0x08 = 1 << 3
Friday = 16, // = 0x10 = 1 << 4
Saturday = 32, // = 0x20 = 1 << 5
Sunday = 64, // = 0x40 = 1 << 6
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
Weekends = Saturday | Sunday
}
バイナリリテラルでは、どのビットが設定されているのがより明白で、16進数とビット単位の算術を理解する必要はありません。
[Flags]
public enum DaysOfWeek
{
Monday = 0b00000001,
Tuesday = 0b00000010,
Wednesday = 0b00000100,
Thursday = 0b00001000,
Friday = 0b00010000,
Saturday = 0b00100000,
Sunday = 0b01000000,
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
Weekends = Saturday | Sunday
}
桁区切り記号
アンダースコア_
は数字区切り記号として使用できます。大きな数値リテラルで数字をグループ化できることは、可読性に大きな影響を与えます。
アンダースコアは、以下に記載されている場合を除き、数値リテラルの任意の場所で発生します。異なるグループ化は、異なるシナリオや異なる数値ベースで意味をなさないかもしれません。
数字の任意のシーケンスは、1つ以上のアンダースコアで区切ることができます。 _
は小数点および指数で使用できます。セパレータには意味的な影響はなく、単に無視されます。
int bin = 0b1001_1010_0001_0100;
int hex = 0x1b_a0_44_fe;
int dec = 33_554_432;
int weird = 1_2__3___4____5_____6______7_______8________9;
double real = 1_000.111_1e-1_000;
_
桁区切り文字を使用できない場合:
- 値の先頭(
_121
) - 値の最後に(
121_
または121.05_
) - 小数点以下(
10_.0
) - 指数文字(
1.1e_1
)の隣に - 型指定子(
10_f
)の隣に - バイナリと16進のリテラルで
0x
または0b
直後( たとえば0b_1001_1000を許可するように変更される可能性があります )
タプルの言語サポート
基本
タプルは、要素の順序付けられた有限リストです。タプルは、タプルの各要素を個別に処理する代わりに、単一のエンティティをまとめて処理し、リレーショナルデータベース内の個々の行(つまりレコード)を表現する手段としてプログラミングで一般的に使用されます。
C#7.0では、メソッドは複数の戻り値を持つことができます。その背後では、コンパイラは新しいValueTuple構造体を使用します。
public (int sum, int count) GetTallies()
{
return (1, 2);
}
サイドノート :Visual Studio 2017でこれを動作させるには、 System.ValueTuple
パッケージを入手する必要があります。
タプルを返すメソッドの結果が単一の変数に代入された場合、メソッドのシグネチャで定義された名前でメンバーにアクセスできます。
var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2
タプル解体
タプル分解はタプルをその部分に分割します。
たとえば、 GetTallies
を呼び出して戻り値を2つの別々の変数に代入すると、その2つの変数にタプルが分解されます。
(int tallyOne, int tallyTwo) = GetTallies();
var
も動作します:
(var s, var c) = GetTallies();
あなたはまたして、短い構文を使用することができvar
の外()
var (s, c) = GetTallies();
既存の変数に分解することもできます:
int s, c;
(s, c) = GetTallies();
スワップははるかに簡単になりました(一時変数は必要ありません):
(b, a) = (a, b);
興味深いことに、どのオブジェクトも、クラス内にDeconstruct
メソッドを定義することで分解できます。
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}
var person = new Person { FirstName = "John", LastName = "Smith" };
var (localFirstName, localLastName) = person;
この場合、 (localFirstName, localLastName) = person
構文がそのperson
Deconstruct
を呼び出しています。
脱構築は拡張法でも定義できます。これは上記と同じです:
public static class PersonExtensions
{
public static void Deconstruct(this Person person, out string firstName, out string lastName)
{
firstName = person.FirstName;
lastName = person.LastName;
}
}
var (localFirstName, localLastName) = person;
Person
クラスの別のアプローチは、 Name
自体をTuple
として定義することです。次の点を考慮してください。
class Person
{
public (string First, string Last) Name { get; }
public Person((string FirstName, string LastName) name)
{
Name = name;
}
}
それで、人をインスタンス化することができます(タプルを引数として取ることができます):
var person = new Person(("Jane", "Smith"));
var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last; // "Smith"
タプルの初期化
コード内にタプルを任意に作成することもできます。
var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John
Console.WriteLine(name.Item2);
// Outputs Smith
タプルを作成するときに、タプルのメンバーに特別な項目名を割り当てることができます。
var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John
型推論
同じシグネチャで定義された複数のタプル(一致する型とカウント)は、一致する型として推論されます。例えば:
public (int sum, double average) Measure(List<int> items)
{
var stats = (sum: 0, average: 0d);
stats.sum = items.Sum();
stats.average = items.Average();
return stats;
}
stats
の宣言以来、戻すことができるstats
変数とメソッドの戻り署名が一致しています。
リフレクションとタプルフィールド名
実行時にメンバー名が存在しません。 Reflectionでは、メンバ名が一致しなくても同じ数とタイプのメンバを持つタプルが同じとみなされます。タプルをobject
変換してから同じメンバ型であるが異なる名前を持つタプルに変換しても、例外は発生しません。
ValueTupleクラス自体はメンバー名の情報を保持しませんが、情報はTupleElementNamesAttributeのリフレクションを通じて使用できます。この属性はタプル自体には適用されず、メソッドのパラメータ、戻り値、プロパティ、およびフィールドに適用されます。これにより、タプル項目名がアセンブリ全体で保持されるようになります。つまり、メソッドが(文字列名、int count)を返す場合、戻り値には値を含むTupleElementNameAttributeがマークされるため、名前の名前とカウントが別のアセンブリのメソッドの呼び出し側で利用可能になります"名前"と "カウント"。
ジェネリックスとasync
使用する
ジェネリックを完全にサポートする新しいタプル機能(基本的なValueTuple
型を使用)はジェネリック型パラメータとして使用できます。そうすれば、 async
/ await
パターンでそれらを使用することができます:
public async Task<(string value, int count)> GetValueAsync()
{
string fooBar = await _stackoverflow.GetStringAsync();
int num = await _stackoverflow.GetIntAsync();
return (fooBar, num);
}
コレクションで使用する
コードの分岐を避ける条件に基づいて一致するタプルを見つけようとしているシナリオで、(例として)タプルのコレクションを持つことは有益になるかもしれません。
例:
private readonly List<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
new Tuple<string, string, string>("test1", "test2", "Value"),
new Tuple<string, string, string>("test1", "test1", "Value2"),
new Tuple<string, string, string>("test2", "test2", "Value3"),
};
public string FindMatchingValue(string firstElement, string secondElement)
{
var result = labels
.Where(w => w.Item1 == firstElement && w.Item2 == secondElement)
.FirstOrDefault();
if (result == null)
throw new ArgumentException("combo not found");
return result.Item3;
}
新しいタプルは次のようになります。
private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = new List<(string firstThingy, string secondThingyLabel, string foundValue)>()
{
("test1", "test2", "Value"),
("test1", "test1", "Value2"),
("test2", "test2", "Value3"),
}
public string FindMatchingValue(string firstElement, string secondElement)
{
var result = labels
.Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement)
.FirstOrDefault();
if (result == null)
throw new ArgumentException("combo not found");
return result.foundValue;
}
上記のサンプルタプルの命名はかなり一般的ですが、関連するラベルの考え方により、 "item1"、 "item2"、および "item3"の参照に対して、コード内で何が試みられているかをより深く理解することができます。
ValueTupleとTupleの違い
ValueTuple
導入の主な理由はパフォーマンスです。
型名 | ValueTuple | Tuple |
---|---|---|
クラスまたは構造 | struct | class |
変異性(作成後の値の変更) | 変更可能な | 不変 |
ネーミング・メンバーおよびその他の言語サポート | はい | いいえ( 未定 ) |
参考文献
ローカル関数
ローカル関数はメソッド内で定義され、メソッドの外部では使用できません。彼らはすべてのローカル変数にアクセスし、iterators、 async
/ await
、lambda構文をサポートします。このようにして、関数に固有の繰り返しは、クラスを混雑させることなく機能化することができます。副作用として、Intellisenseの提案のパフォーマンスが向上します。
例
double GetCylinderVolume(double radius, double height)
{
return getVolume();
double getVolume()
{
// You can declare inner-local functions in a local function
double GetCircleArea(double r) => Math.PI * r * r;
// ALL parents' variables are accessible even though parent doesn't have any input.
return GetCircleArea(radius) * height;
}
}
ローカル関数はLINQ演算子のコードを大幅に単純化します。通常、引数チェックと実際のロジックを分離して、反復開始後まで引数チェックを瞬時に行う必要があります。
例
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
return iterator();
IEnumerable<TSource> iterator()
{
foreach (TSource element in source)
if (predicate(element))
yield return element;
}
}
ローカル関数はasync
およびawait
キーワードもサポートしてawait
ます。
例
async Task WriteEmailsAsync()
{
var emailRegex = new Regex(@"(?i)[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z0-9-.]+");
IEnumerable<string> emails1 = await getEmailsFromFileAsync("input1.txt");
IEnumerable<string> emails2 = await getEmailsFromFileAsync("input2.txt");
await writeLinesToFileAsync(emails1.Concat(emails2), "output.txt");
async Task<IEnumerable<string>> getEmailsFromFileAsync(string fileName)
{
string text;
using (StreamReader reader = File.OpenText(fileName))
{
text = await reader.ReadToEndAsync();
}
return from Match emailMatch in emailRegex.Matches(text) select emailMatch.Value;
}
async Task writeLinesToFileAsync(IEnumerable<string> lines, string fileName)
{
using (StreamWriter writer = File.CreateText(fileName))
{
foreach (string line in lines)
{
await writer.WriteLineAsync(line);
}
}
}
}
あなたが気づいたかもしれない重要なことの1つは、ローカル関数をreturn
文の下で定義することができ、その上に定義する必要はないということです。さらに、ローカル関数は通常、 "lowerCamelCase"命名規則に従い、クラススコープ関数との区別を容易にします。
パターンマッチング
C#のパターンマッチング拡張は、関数型言語からのパターンマッチングの利点の多くを可能にしますが、基になる言語の感触とスムーズに統合します
switch
式
パターンマッチングは、 switch
文を拡張して型をオンにします。
class Geometry {}
class Triangle : Geometry
{
public int Width { get; set; }
public int Height { get; set; }
public int Base { get; set; }
}
class Rectangle : Geometry
{
public int Width { get; set; }
public int Height { get; set; }
}
class Square : Geometry
{
public int Width { get; set; }
}
public static void PatternMatching()
{
Geometry g = new Square { Width = 5 };
switch (g)
{
case Triangle t:
Console.WriteLine($"{t.Width} {t.Height} {t.Base}");
break;
case Rectangle sq when sq.Width == sq.Height:
Console.WriteLine($"Square rectangle: {sq.Width} {sq.Height}");
break;
case Rectangle r:
Console.WriteLine($"{r.Width} {r.Height}");
break;
case Square s:
Console.WriteLine($"{s.Width}");
break;
default:
Console.WriteLine("<other>");
break;
}
}
is
式は、
パターンマッチングは、 is
演算子を拡張して型をチェックし、同時に新しい変数を宣言します。
例
string s = o as string;
if(s != null)
{
// do something with s
}
次のように書き直すことができます。
if(o is string s)
{
//Do something with s
};
また、パターン変数s
スコープは、囲みスコープの終わりに達するif
ブロックの外側に拡張されています(例:
if(someCondition)
{
if(o is string s)
{
//Do something with s
}
else
{
// s is unassigned here, but accessible
}
// s is unassigned here, but accessible
}
// s is not accessible here
refリターンとrefローカル
Ref戻り値と参照先は、安全でないポインタに頼らずにメモリをコピーするのではなく、メモリブロックへの参照を操作して戻すのに便利です。
リフレクションリターン
public static ref TValue Choose<TValue>(
Func<bool> condition, ref TValue left, ref TValue right)
{
return condition() ? ref left : ref right;
}
これにより、2つの値を参照で渡すことができます。そのうちの1つは、ある条件に基づいて返されます。
Matrix3D left = …, right = …;
Choose(chooser, ref left, ref right).M20 = 1.0;
Refローカル
public static ref int Max(ref int first, ref int second, ref int third)
{
ref int max = first > second ? ref first : ref second;
return max > third ? ref max : ref third;
}
…
int a = 1, b = 2, c = 3;
Max(ref a, ref b, ref c) = 4;
Debug.Assert(a == 1); // true
Debug.Assert(b == 2); // true
Debug.Assert(c == 4); // true
安全でない参照操作
System.Runtime.CompilerServices.Unsafe
では、基本的にポインタであるかのようにref
値を操作できる安全でない操作が定義されています。
たとえば、メモリアドレス( ref
)を別のタイプとして再解釈すると、次のようになります。
byte[] b = new byte[4] { 0x42, 0x42, 0x42, 0x42 };
ref int r = ref Unsafe.As<byte, int>(ref b[0]);
Assert.Equal(0x42424242, r);
0x0EF00EF0;
Assert.Equal(0xFE, b[0] | b[1] | b[2] | b[3]);
ただし、これを行う際にはエンディアンに注意してBitConverter.IsLittleEndian
。必要に応じてBitConverter.IsLittleEndian
チェックし、 BitConverter.IsLittleEndian
応じて処理してBitConverter.IsLittleEndian
。
または、安全でない方法で配列を反復処理する:
int[] a = new int[] { 0x123, 0x234, 0x345, 0x456 };
ref int r1 = ref Unsafe.Add(ref a[0], 1);
Assert.Equal(0x234, r1);
ref int r2 = ref Unsafe.Add(ref r1, 2);
Assert.Equal(0x456, r2);
ref int r3 = ref Unsafe.Add(ref r2, -3);
Assert.Equal(0x123, r3);
または同様のSubtract
:
string[] a = new string[] { "abc", "def", "ghi", "jkl" };
ref string r1 = ref Unsafe.Subtract(ref a[0], -2);
Assert.Equal("ghi", r1);
ref string r2 = ref Unsafe.Subtract(ref r1, -1);
Assert.Equal("jkl", r2);
ref string r3 = ref Unsafe.Subtract(ref r2, 3);
Assert.Equal("abc", r3);
さらに、2つのref
値が同じかどうか、つまり同じアドレスであるかどうかを確認できます。
long[] a = new long[2];
Assert.True(Unsafe.AreSame(ref a[0], ref a[0]));
Assert.False(Unsafe.AreSame(ref a[0], ref a[1]));
リンク
githubのSystem.Runtime.CompilerServices.Unsafe
式を投げる
C#7.0では、特定の場所で式としてスローすることができます。
class Person
{
public string Name { get; }
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();
}
C#7.0以前では、式の本体から例外をスローしたい場合は、次のようにする必要があります。
var spoons = "dinner,desert,soup".Split(',');
var spoonsArray = spoons.Length > 0 ? spoons : null;
if (spoonsArray == null)
{
throw new Exception("There are no spoons");
}
または
var spoonsArray = spoons.Length > 0
? spoons
: new Func<string[]>(() =>
{
throw new Exception("There are no spoons");
})();
C#7.0では、上記は以下のように単純化されました。
var spoonsArray = spoons.Length > 0 ? spoons : throw new Exception("There are no spoons");
拡張表現のボディメンバーリスト
C#7.0では、式本体を持つことができるもののリストにアクセサ、コンストラクタ、ファイナライザを追加しています。
class Person
{
private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
private int id = GetId();
public Person(string name) => names.TryAdd(id, name); // constructors
~Person() => names.TryRemove(id, out _); // finalizers
public string Name
{
get => names[id]; // getters
set => names[id] = value; // setters
}
}
破棄演算子のout var宣言セクションも参照してください。
ValueTask
Task<T>
はクラスであり、結果がすぐに利用可能になったときに割り当ての不要なオーバーヘッドが発生します。
ValueTask<T>
は構造体であり、 非同期操作の結果がValueTask<T>
時にすでに使用可能な場合にTask
オブジェクトの割り当てを防止するために導入されました。
したがって、 ValueTask<T>
は2つの利点があります。
1.パフォーマンスの向上
ここにTask<T>
例があります:
- ヒープ割り当てが必要
- JITで120nsかかる
async Task<int> TestTask(int d)
{
await Task.Delay(d);
return 10;
}
ここに、アナログValueTask<T>
例があります:
- いいえヒープ割り当てない結果が(それが理由で、この場合にはされていない同期知られている場合
Task.Delay
が、多くの場合、多くの実世界の中でasync
/await
シナリオ) - JITで65nsかかる
async ValueTask<int> TestValueTask(int d)
{
await Task.Delay(d);
return 10;
}
2.実装の柔軟性の向上
同期したい非同期インターフェースの実装は、さもなければ、 Task.Run
またはTask.FromResult
いずれかを使用するように強制される(上記のパフォーマンスペナルティが生じる)。したがって、同期実装に対するいくつかの圧力があります。
しかし、 ValueTask<T>
では、呼び出し側に影響を与えることなく、実装が同期または非同期のどちらかを自由に選択できます。
たとえば、非同期メソッドを使用したインタフェースを次に示します。
interface IFoo<T>
{
ValueTask<T> BarAsync();
}
...そのメソッドが呼び出される方法は次のとおりです。
IFoo<T> thing = getThing();
var x = await thing.BarAsync();
ValueTask
では、上記のコードは、同期実装または非同期実装のどちらでも動作します 。
同期実装:
class SynchronousFoo<T> : IFoo<T>
{
public ValueTask<T> BarAsync()
{
var value = default(T);
return new ValueTask<T>(value);
}
}
非同期実装
class AsynchronousFoo<T> : IFoo<T>
{
public async ValueTask<T> BarAsync()
{
var value = default(T);
await Task.Delay(1);
return value;
}
}
ノート
ValueTask
構造体はC#7.0に追加される予定でしたが、当面は別のライブラリとして保管されています。 ValueTask <T> System.Threading.Tasks.Extensions
パッケージは、 Nuget Galleryからダウンロードできます。