(この記事を読むのに15分かかります。
C#10が. NET 6およびVisual Studio 2022の一部としてリリースされたことをお知らせします。この記事では、コードをより美しく、表現力豊かに、高速にするC#10の新機能の多くを紹介します。
インストール方法などの詳細については、Visual Studio 2022 Bulletinおよび. NET 6 Bulletinをご覧ください。
グローバルおよび暗黙的な利用
usingディレクティブは、名前空間の使用方法を簡素化します。C#10には新しいグローバルusingディレクティブと暗黙的usingsが含まれており、各ファイルの先頭に指定する必要があるusingの数を減らします。
グローバルusingディレクティブ
キーワードglobalがusingディレクティブの前にある場合、usingはプロジェクト全体に適用されます。
global using System;
グローバルusingディレクティブでusingの任意の機能を使用できます。たとえば、静的インポート型を追加し、その型のメンバーとネストされた型をプロジェクト全体で使用できるようにします。usingディレクティブでエイリアスを使用すると、そのエイリアスはプロジェクト全体にも影響します。
global using static System.Console;
global using Env = System.Environment;
グローバル使用は、Program.csやglobalusings.csのような特別な名前のファイルを含む任意の.csファイルに配置できます。グローバルusingsのスコープは現在のコンパイル済みで、通常は現在のプロジェクトに対応します。
詳細は、グローバルusingディレクティブを参照してください。
暗黙的な使用
暗黙的using機能は、ビルドしているプロジェクトタイプに共通のグローバルusingディレクティブを自動的に追加します。暗黙的なusingsを有効にするには、.csprojファイルのImplicitUsingsプロパティーを設定します。
<PropertyGroup>
<!-- Other properties like OutputType and TargetFramework -->
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
新しい. NET 6テンプレートで暗黙の使用が有効になりました。. NET 6テンプレートの変更の詳細は、このブログ記事をご覧ください。
特定のグローバル使用命令セットの一部は、ビルドするアプリケーションの種類によって異なります。たとえば、コンソールアプリケーションやクラスライブラリの暗黙的な使用は、ASP.NETアプリケーションの暗黙的な使用とは異なります。 詳細については、この暗黙的な使用に関する記事を参照してください。
使用の組み合わせ
ファイルの先頭にある伝統的なusingディレクティブ、グローバルなusingディレクティブ、および暗黙的なusingはうまく連携します。暗黙的に使用すると、ビルドしているプロジェクトのタイプに適した. NET名前空間をプロジェクトファイルに含めることができます。グローバルusingディレクティブを使用すると、追加の名前空間を含めて、プロジェクト全体で使用できるようにできます。コードファイルの上部にあるusingディレクティブを使用すると、プロジェクト内の少数のファイルだけが使用する名前空間を含めることができます。
定義にかかわらず、追加のusingディレクティブは名前解決の曖昧さの可能性を高めます。このような状況になった場合は、エイリアスを追加するか、インポートする名前空間の数を減らすことを検討してください。たとえば、グローバルなusingディレクティブを、ファイルのサブセットの先頭にある明示的なusingディレクティブに置き換えることができます。
暗黙の使用によって含まれる名前空間を削除する必要がある場合は、プロジェクトファイルで指定できます。
<ItemGroup>
<Using Remove="System.Threading.Tasks" />
</ItemGroup>
また、グローバルなusingディレクティブであるかのように名前空間を追加することもできます。Usingアイテムをプロジェクトファイルに追加できます。
<ItemGroup>
<Using Include="System.IO.Pipes" />
</ItemGroup>
ファイル范囲の名前
多くのファイルには単一の名前空間のコードが含まれる。C#10以降では、中括弧なしでセミコロンの後に名前空間を文として含めることができます。
namespace MyCompany.MyNamespace;
class MyClass // Note: no indentation
{ ... }
コードを簡素化し、ネストレベルを削除した。ファイルスコープの名前空間宣言は1つだけ許可され、型を宣言する前に存在しなければなりません。
ファイル範囲名前空間の詳細については、名前空間キーワードの記事を参照してください。
ラムダ式とメソッドグループの改善
ラムダの構文と型にいくつかの改善が加えられました。これらが広く役立つことを期待しており、推進シナリオの1つは、ASP.NET Minimal APIをシンプルにすることです。
ラムダの自然な型
ラムダ式は時々“自然な”型を持つ。つまり、コンパイラは通常ラムダ式の型を推論できる。
ここまでは、ラムダ式をデリゲート型または式型に変換する必要がありました。ほとんどの場合、BCLではオーバーロードされたFunc<...>またはAction<...>デリゲート型のいずれかを使用します:
Func<string, int> parse = (string s) => int.Parse(s);
しかし、C#10以降では、ラムダにそのような“ターゲット型”がない場合、次のように計算しようとします。
var parse = (string s) => int.Parse(s);
お好みのエディタでvar parseにカーソルを合わせると、型がFunc<string int>のままであることを確認できます。一般的に、コンパイラーは利用可能なFuncまたはActionデリゲートを使用します(適切なデリゲートが存在する場合)。それ以外の場合は、デリゲート型を合成します(refパラメータがある場合や、引数がたくさんある場合など)。
すべてのラムダ式が自然な型を持つわけではなく、十分な型情報を持たないものもある。例えば、パラメータ型を破棄すると、コンパイラーはどのデリゲート型を使用するかを決定できなくなります。
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
ラムダの自然な型付けは、オブジェクトやデリゲートのような弱い型に割り当てることができることを意味します。
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>
式ツリーに関しては、“target”型と“nature”型を組み合わせています。ターゲット型がLambdaExpressionまたは非ジェネリックExpression(すべての式ツリーの基底型)で、ラムダが自然なデリゲート型Dを持つ場合、代わりにExpressionを生成します
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
メソッドグループの自然なタイプ
メソッドグループ(すなわち、引数リストを持たないメソッド名)も自然型を持つことがあります。メソッドグループはいつでも互換性のあるデリゲート型に変換できます。
Func<int> read = Console.Read;
Action<string> write = Console.Write;
メソッドグループにオーバーロードが1つしかない場合、自然型になります。
var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose
ラムダの戻り値の型
前の例では、ラムダ式の戻り値の型は明らかであり、推論されています。必ずしもそうではない:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
C#10では、メソッドやローカル関数と同じように、ラムダ式に戻り値の型を明示的に指定できます。戻り値の型はパラメータの前にあります。明示的な戻り値の型を指定する場合、コンパイラーや他の開発者が混乱しないように、引数は括弧で囲む必要があります。
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
ラムダ上の属性
C#10以降では、メソッドやローカル関数と同じように、ラムダ式にプロパティを配置できます。プロパティがある場合、ラムダの引数リストは括弧で囲む必要があります:
Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";
ネイティブ関数と同様に、AttributeTargets.Method 上で有効であれば、属性をラムダに適用できます。
ラムダはメソッドやローカル関数とは異なる方法で呼び出されるため、ラムダが呼び出されたときにプロパティは影響しません。しかし、ラムダのプロパティはコード解析に有用であり、リフレクションによって見つけることができます。
構造物の改善
C#10では、strsとクラスの间のパリティを向上させるがされています。これらの新機能には、パラメータレスコンストラクタ、フィールド初期化子、レコード構造、with式が含まれます。
0 1パラメータなし構造体コンストラクタとフィールド初期値設定子
C#10以前は、各構造体には暗黙的なパラメータなしコンストラクタがあり、構造体のフィールドをデフォルト値に設定していました。構造体に引数なしコンストラクタを作成するのは間違いです。
C#10以降では、独自の引数なし構造コンストラクタを含めることができます。指定しない場合、すべてのフィールドをデフォルト値に設定する暗黙的な引数なしコンストラクタが提供されます。構造体で作成するパラメータなしコンストラクタはパブリックでなければならず、部分的ではありません。
public struct Address
{
public Address()
{
City = "<unknown>";
}
public string City { get; init; }
}
フィールドは、上記のようにパラメータなしコンストラクタで初期化するか、フィールドまたはプロパティ初期化子を使用して初期化できます。
public struct Address
{
public string City { get; init; } = "<unknown>";
}
デフォルトで作成された構造体、または配列割り当ての一部として作成された構造体は、明示的な引数なしコンストラクタを無視し、常に構造体メンバーをデフォルト値に設定します。構造体のパラメータなしコンストラクタの詳細は、構造体の型を参照してください。
02 Record structs
C#10以降、record structを使ってrecordを定義できるようになりました。これらはC#9で導入されたレコードクラスに似ています:
public record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
レコード·クラスは引き続きrecordを使用して定義できます。また、recordクラスを使用して明確に説明できます。
構造体はすでに等しい値を持っています。それらを比較すると、値によって決まります。レコード構造IEQuatable
レコード構造は位置構造であり、メインコンストラクタは暗黙的にpublicメンバを宣言します:
public record struct Person(string FirstName, string LastName);
メインコンストラクタの引数は、レコード構造体のパブリック自動実装プロパティになります。recordクラスとは異なり、暗黙的に作成されるプロパティは読み取り/書き込みです。これによりタプルを名前付き型に変換しやすくなります。戻り値の型をstring FirstName string LastNameなどのタプルからPersonの名前付き型に変更すると、コードがクリーンアップされ、メンバー名が一貫しているようになります。位置レコード構造の宣言は簡単で、可変セマンティクスを維持します。
プライマリコンストラクタ引数と同じ名前のプロパティまたはフィールドを宣言した場合、自動プロパティは合成されず、独自のものが使用されます。
不変のレコード構造を作成するには、構造にreadonlyを追加するか任意の構造に追加できるように、個々の属性にreadonlyを適用します。オブジェクト初期化子は、読み取り専用プロパティを設定できる構築段階の一部です。これは不変レコード構造を使用する1つの方法です:
var person = new Person { FirstName = "Mads", LastName = "Torgersen"};
public readonly record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
レコード構造の詳細については、この記事をご覧ください。
03 RecordクラスのToString上のシール子
レコードクラスも改善された。C#10以降、ToStringメソッドにseal修飾子を含めることができます。これにより、コンパイラーは派生レコードに対してToString実装を合成できなくなります。
ToStringのは、こののレコードを参照してください。
0 4構造体と匿名型式
C#10は、レコード構造と匿名型を含むすべての構造体のwith式をサポートしています。
var person2 = person with { LastName = "Kristensen" };
新しい値を持つ新しいインスタンスを返します。値はいくつでも更新できます。設定しなかった値は、最初のインスタンスと同じ値を保持します。
withの詳細についてはこちらをご覧ください。
内挿文字列の改善
C#に補間文字列を追加すると、パフォーマンスと表現力の面でこの構文を使う方がはるかに多くのことができると感じます。
01文字列の補間ハンドラ
今日、コンパイラーは補間文字列をstring.Formatへの呼び出しに変換します。これにより、パラメータのボックス化、パラメータの配列の割り当て、そしてもちろん結果の文字列自体など、多くの割り当てが発生します。さらに、実際の補間の意味で操作の余地はありません。
C#10では、補間文字列パラメータ式の処理をAPIが“引き継ぐ”ことができるライブラリパターンを追加しました。たとえば、StringBuilder.Appendを考えてみます。
var sb = new StringBuilder();
sb.Append($"Hello {args[0]}, how are you?");
ここまでは、新しく割り当てられた文字列と計算された文字列を使用してAppend stringを呼び出します。value StringBuilderのブロックに追加するオーバーロード。ただし、Appendには新しいオーバーロードAppend refStringBuilder.AppendInterpolatedStringHandler handlerが追加されました。これは、補間文字列を引数として使用する場合に、文字列オーバーロードよりも優先されます。 多くの場合、SomethingInterpolatedStringHandlerの形でパラメータ型を見ると、API作成者はその目的に合わせて補間文字列をより適切に処理するために、舞台裏で何らかの作業を行います。Appendの例では、文字列“Hello”、args[0]、“How are you?”があります。StringBuilderに個別にアタッチされるため、効率的で同じ結果が得られます。
特定の条件下で文字列を構築したい場合があります。例はDebug.Assertです:
Debug.Assert(condition, $"{SomethingExpensiveHappensHere()}");
ほとんどの場合、条件は真であり、2番目の引数は使用されません。しかし、呼び出しごとにすべてのパラメータが評価されるため、実行が不必要に遅くなります。Debug.Assertにはカスタム補間文字列ビルダーを持つオーバーロードがあり、条件がfalseでない限り第2引数が評価されないようにします。
最後に、指定された呼び出しで文字列補間の動作を実際に変更する例を示します。String.Createを使用すると、IFormatProviderが補間文字列パラメータ自体の穴をフォーマットするために使用する式を指定できます。
String.Create(CultureInfo.InvariantCulture, $"The result is {result}");
補間文字列ハンドラの詳細については、この記事およびカスタムハンドラの作成に関するこのチュートリアルをご覧ください。
02定数の補間文字列
文字列を補間するすべての穴が定数文字列である場合、結果の文字列も定数になります。これにより、プロパティなど、より多くの場所で文字列補間構文を使用できます。
[Obsolete($"Call {nameof(Discard)} instead")]
穴は定数文字列で埋める必要があることに注意してください。数値や日付値などの他の型は、文化的に敏感であり、コンパイル時に計算できないため使用できません。
その他の改善点
C#10は言語全体に多くの小さな改良を加えている。C#を期待通りに動作させることもあります。
分解における宣言と変数の混合
C#10以前は、脱構築はすべての変数を新規にするか、すべての変数を事前に宣言する必要があった。C#10では、以下を混合できます。
int x2;
int y2;
(x2, y2) = (0, 1); // Works in C# 9
(var x, var y) = (0, 1); // Works in C# 9
(x2, var y3) = (0, 1); // Works in C# 10 onwards
詳細は解体の記事をご覧ください。
明確な分布の改善
明示的に割り当てられていない値を使用すると、C#はエラーを生成します。C#10はコードをよりよく理解し、偽のエラーが少なくなります。これらの同じ改善により、空の参照に対する偽のエラーや警告が少なくなります。
C#の割り当て決定の詳細については、C#10の新機能記事を参照してください。
拡張属性パターン
C#10では拡张属性モードが追加され、モード内のネストされた属性値により简単にアクセスできるようになった。例えば、上記のPersonレコードにアドレスを追加すると、次の2つの方法でパターンマッチングを行うことができます。
object obj = new Person
{
FirstName = "Kathleen",
LastName = "Dollard",
Address = new Address { City = "Seattle" }
};
if (obj is Person { Address: { City: "Seattle" } })
Console.WriteLine("Seattle");
if (obj is Person { Address.City: "Seattle" }) // Extended property pattern
Console.WriteLine("Seattle");
拡張属性モードは、コードを簡素化し、特に複数の属性を一致させる場合に読みやすくします。
拡張属性パターンの詳細は、パターンマッチングの記事を参照してください。
呼び出し元の式プロパティ
CallerArgumentExpressionAttributeメソッド呼び出しコンテキストに関する情報を提供します。他のCompilerServicesプロパティと同様に、このプロパティはオプションのパラメータに適用されます。この場合、1つの文字列は:
void CheckExpression(bool condition,
[CallerArgumentExpression("condition")] string? message = null )
{
Console.WriteLine($"Condition: {message}");
}
CallerArgumentExpressionに渡されるパラメータ名は、異なるパラメータの名前になります。引数として渡された式は、文字列に含まれます。例えば、
var a = 6;
var b = true;
CheckExpression(true);
CheckExpression(b);
CheckExpression(a > 5);
// Output:
// Condition: true
// Condition: b
// Condition: a > 5
ArgumentNullException.ThrowIfNullは、このプロパティの使用方法の良い例です。パラメータ名を渡す必要がないように、デフォルトで値を提供します。
void MyMethod(object value)
{
ArgumentNullException.ThrowIfNull(value);
}
以下にメッセージを残してください、あなたの提案やアイデアを教えてください。