精選:C# の文法機能まとめ

精選:C# の文法機能まとめ

C# 10 は .NET 6、VS2022 と共にリリースされました。本記事では .NET のリリース順に従い、Microsoft 公式ドキュメントをもとに C# の興味深い文法機能を整理します。

最終更新 2021/11/19 17:38
louzixl
読了目安 15 分
カテゴリ
.NET
タグ
.NET C#

C# 10 は .NET 6、VS2022 と共にリリースされました。本記事では .NET のリリース順に従い、Microsoft 公式ドキュメント に基づいて C# の興味深い構文機能を整理します。

注:異なる .NET プラットフォームに基づいて作成されたプロジェクトでは、デフォルトでサポートされる C# のバージョンが異なります。以下で説明する構文機能では、導入された C# のバージョンを示します。使用する際には、使用している C# のバージョンが対応する機能をサポートしているかに注意してください。C# の言語バージョン管理については、公式ドキュメント を参照してください。

匿名関数

匿名関数は C# 2 で導入された機能で、名前の通りメソッド本体のみで名前を持ちません。匿名関数は delegate を使用して作成され、デリゲートに変換できます。匿名関数は戻り値の型を指定する必要はなく、return 文に基づいて自動的に戻り値の型が判断されます。

注:C# 3 以降ではラムダ式が導入され、ラムダを使用することでより簡潔に匿名関数を作成できます。可能な限りラムダを使用して匿名関数を作成することを推奨します。ラムダとは異なり、delegate を使用して匿名関数を作成する場合、パラメーターリストを省略でき、任意のパラメーターリストを持つデリゲート型に変換できます。

// delegateキーワードを使用して作成。戻り値の指定不要、デリゲートに変換可能、パラメーターリストを省略可能(ラムダとは異なる)
Func<int, bool> func = delegate { return true; };

自動プロパティ

C# 3 から、プロパティアクセサーに他のロジックが必要ない場合、自動プロパティを使用してより簡潔にプロパティを宣言できます。コンパイル時に、コンパイラは get、set アクセサーからのみアクセス可能なプライベートな匿名フィールドを作成します。VS で開発する場合、スニペットコード prop + 2 回 Tab キーで自動プロパティをすばやく生成できます。

// プロパティの従来の書き方
private string _name;
public string Name
{
    get { return _name; }
    set { _name = value; }
}
// 自動プロパティ
public string Name { get; set; }

また、C# 6 以降では、自動プロパティを初期化できます。

public string Name { get; set; } = "Louzi";

匿名型

匿名型は C# 3 以降で導入された機能で、型を明示的に定義することなく、一連の読み取り専用プロパティを単一のオブジェクトにカプセル化します。コンパイラは匿名型の各プロパティの型を自動的に推論し、型名を生成します。CLR の観点から見ると、匿名型は他の参照型と変わりなく、匿名型は直接 object から派生します。2 つ以上の匿名オブジェクトが順序、名前、型が同じプロパティを指定した場合、コンパイラはそれらを同じ型のインスタンスと見なします。匿名型を作成する際にメンバー名を指定しない場合、コンパイラはプロパティの初期化に使用された名前をプロパティ名として使用します。

匿名型は主に LINQ クエリの select クエリ式で使用されます。匿名型は new と初期化リストを使用して作成します。

// new と初期化リストを使用して匿名型を作成
var person = new { Name = "Louzi", Age = 18 };
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
// LINQ での使用
var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };
foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

LINQ

C# 3 では、強力な機能であるクエリ式、つまり統合言語クエリ(LINQ)が導入されました。クエリ式はクエリ構文でクエリを表現し、SQL に似た構文で記述された一連の句で構成されます。

クエリ式は from 句で始まり、select または group 句で終了する必要があります。最初の from 句と最後の select または group 句の間に、whereorderbyjoinlet、他の from 句などを含めることができます。

SQL データベース、XML ドキュメント、ADO.NET データセット、および IEnumerable または IEnumerable インターフェースを実装するコレクションオブジェクトに対して LINQ クエリを実行できます。

完全なクエリには、データソースの作成、クエリ式の定義、クエリの実行が含まれます。クエリ式変数はクエリ結果ではなくクエリを格納し、クエリ変数を反復処理した後でのみクエリが実行されます。

クエリ構文で表現できるクエリはすべてメソッド構文でも表現できますが、読みやすいクエリ構文を使用することをお勧めします。一部のクエリ操作(Count や Max など)には同等のクエリ式句がないため、メソッド呼び出しを使用する必要があります。メソッド呼び出しとクエリ構文を組み合わせて使用することもできます。

LINQ の詳細については、Microsoft 公式ドキュメント を参照してください。

// データソース
int[] scores = { 90, 71, 82, 93, 75, 82 };
// クエリ式
IEnumerable<int> scoreQuery = // クエリ変数
    from score in scores // 必須
    where score > 80 // 省略可能
    orderby score descending // 省略可能
    select score; // select または group で終了する必要あり
// クエリを実行して結果を生成
foreach (int testScore in scoreQuery)
{
    Console.WriteLine(testScore);
}

ラムダ

C# 3 では、自動プロパティ、拡張メソッド、暗黙の型指定、LINQ、そしてラムダ式など、多くの強力な機能が導入されました。

ラムダ式を作成するには、=> の左側に入力パラメーターを指定し(空の括弧はゼロ個のパラメーターを意味し、1 つのパラメーターの場合は括弧を省略可)、右側に式またはステートメントブロック(通常 2、3 文)を指定します。すべてのラムダ式はデリゲート型に変換できます。式ラムダは式ツリーにも変換できます(ステートメントラムダは不可)。

匿名関数はパラメーターリストを省略できますが、ラムダで使用しないパラメーターは破棄(C# 9)を使用して指定できます。

asyncawait を使用して、非同期処理を含むラムダ式とステートメントを作成できます(C# 5)。

C# 10 以降では、コンパイラが戻り値の型を推論できない場合、パラメーターの前にラムダ式の戻り値の型を指定できます。その場合、パラメーターは括弧で囲む必要があります。

// ラムダをデリゲートに変換
Func<int, int> square = x => x * x;
// ラムダを式ツリーに変換
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
// 使用しないパラメーターを破棄で指定
Func<int, int, int> constant = (_, _) => 42;
// 非同期ラムダ
var lambdaAsync = async () => await JustDelayAsync();
Console.WriteLine($"main thread id: {Thread.CurrentThread.ManagedThreadId}");
lambdaAsync();
static async Task JustDelayAsync()
{
    await Task.Delay(1000);
    Console.WriteLine($"JustDelayAsync thread id: {Thread.CurrentThread.ManagedThreadId}");
}
// 戻り値の型を指定。指定しないとエラー
var choose = object (bool b) => b ? 1 : "two";

拡張メソッド

拡張メソッドも C# 3 で導入された機能で、既存の型にメソッドを追加でき、元の型を変更する必要はありません。拡張メソッドは静的メソッドですが、インスタンスオブジェクトの構文で呼び出され、最初のパラメーターはメソッドが操作する型を指定し、this で修飾します。コンパイラは IL にコンパイルする際に静的メソッドの呼び出しに変換します。

型に拡張メソッドと同じ名前とシグネチャのメソッドが存在する場合、コンパイラは型のメソッドを選択します。コンパイラはメソッド呼び出し時に、まずその型のインスタンスメソッドを探し、見つからなければその型の拡張メソッドを検索します。

最も一般的な拡張メソッドは LINQ で、既存の System.Collections.IEnumerable および System.Collections.Generic.IEnumerable<T> 型にクエリ機能を追加します。

構造体に拡張メソッドを追加する場合、値渡しのため、構造体オブジェクトのコピーのみ変更できます。C# 7.2 以降では、最初のパラメーターに ref 修飾子を追加して参照渡しにでき、構造体オブジェクト自体を変更できます。

static class MyExtensions
{
    public static void OutputStringExtension(this string s) => Console.WriteLine($"output: {s}");
    public static void OutputPointExtension(this Point p)
    {
        p.X = 10;
        p.Y = 10;
        Console.WriteLine($"output: ({p.X}, {p.Y})");
    }
    public static void OutputPointWithRefExtension(ref this Point p)
    {
        p.X = 20;
        p.Y = 20;
        Console.WriteLine($"output: ({p.X}, {p.Y})");
    }
}
// クラスの拡張メソッド
"Louzi".OutputStringExtension();
// 構造体の拡張メソッド
Point p = new Point(5, 5);
p.OutputPointExtension(); // output: (10, 10)
Console.WriteLine($"original point: ({p.X}, {p.Y})");  // output: (5, 5)
p.OutputPointWithRefExtension();  // output: (20, 20)
Console.WriteLine($"original point: ({p.X}, {p.Y})");  // output: (20, 20)

暗黙の型指定(var)

C# 3 以降、メソッドスコープ内で暗黙の型指定変数(var)を宣言できます。暗黙の型は強い型付けであり、型はコンパイラによって決定されます。

var はコンストラクターを呼び出してオブジェクトインスタンスを作成する際によく使用されます。C# 9 以降では、このシナリオで決定型の new 式も使用できます。

// 暗黙の型
var s = new List<int>();
// new 式
List<int> ss = new();

注:匿名型を返す場合、var のみ使用できます。

オブジェクト/コレクション初期化子

C# 3 以降、単一のステートメントでオブジェクトまたはコレクションをインスタンス化し、メンバー割り当てを実行できます。

オブジェクト初期化子を使用すると、オブジェクト作成時にオブジェクトのアクセス可能なフィールドまたはプロパティに値を割り当てることができ、コンストラクターパラメーターを指定したり、引数や括弧を省略したりできます。

public class Person
{
    // 自動プロパティ
    public int Age { get; set; }
    public string Name { get; set; }
    public Person() { }
    public Person(string name)
    {
        Name = name;
    }
}
var p1 = new Person { Age = 18, Name = "Louzi" };
var p2 = new Person("Sherilyn") { Age = 18 };

C# 6 以降では、オブジェクト初期化子はアクセス可能なフィールドとプロパティだけでなく、インデクサーも設定できます。

public class MyIntArray
{
    public int CurrentIndex { get; set; }
    public int[] data = new int[3];
    public int this[int index]
    {
        get => data[index];
        set => data[index] = value;
    }
}
var myArray = new MyIntArray { [0] = 1, [1] = 3, [2] = 5, CurrentIndex = 0 };

コレクション初期化子では、1 つ以上の初期値を指定できます。

var persons = new List<Person>
{
    new Person { Age = 18, Name = "Louzi" },
    new Person { Age = 18, Name = "Sherilyn" }
};

組み込みのジェネリックデリゲート

.NET Framework 3.5/4.0 では、それぞれ組み込みの Action および Func ジェネリックデリゲート型が提供されています。void 戻り値型のデリゲートには Action 型を使用でき、Action のバリアントは最大 16 個のパラメーターを持ちます。戻り値のあるデリゲートには Func 型を使用でき、Func 型のバリアントも最大 16 個のパラメーターを持ち、戻り値の型は Func 宣言の最後の型パラメーターです。

Action<int> actionInstance = ActionInstance;
Func<int, string> funcInstance = FuncInstance;
static void ActionInstance(int n) => Console.WriteLine($"input: {n}");
static string FuncInstance(int n) => $"param: {n}";

dynamic

C# 4 の主要な機能は dynamic キーワードの導入です。dynamic 型は、変数の使用とそのメンバー参照時にコンパイル時の型チェックをバイパスし、実行時に解決されます。これにより、JavaScript などの動的型付け言語と同様の構造が実現されます。

dynamic dyn = 1;
Console.WriteLine(dyn.GetType()); // output: System.Int32
dyn = dyn + 3; // dyn が object 型の場合、この行はエラーになる

名前付き引数と省略可能な引数

C# 4 では、名前付き引数と省略可能な引数が導入されました。名前付き引数では、実引数を対応する仮引数に名前で指定できるため、引数リストの位置を一致させる必要がありません。省略可能な引数では、パラメーターに既定値を指定することで、実引数を省略できます。省略可能な引数はパラメーターリストの末尾に配置する必要があり、一連の省略可能な引数のいずれかに実引数を指定する場合は、その前に位置するすべての省略可能な引数にも実引数を指定する必要があります。

OptionalAttribute 属性を使用して省略可能な引数を宣言することもでき、その場合は仮引数に既定値を指定する必要はありません。

// 名前付き引数と省略可能な引数
PrintPerson(age: 18, name: "Louzi");
// static void PrintPerson(string name, int age, [Optional, DefaultParameterValue("男")] string sex)
static void PrintPerson(string name, int age, string sex = "男") =>
    Console.WriteLine($"name: {name}, age: {age}, sex: {sex}");

静的インポート

C# 6 では静的インポート機能が導入されました。using static ディレクティブを使用して型をインポートすると、型名を指定せずにその静的メンバーと入れ子になった型にアクセスできます。これにより、型名の繰り返しによる晦渋なコードを回避できます。

using static System.Console;
WriteLine("Hello CSharp");

例外フィルター(when)

C# 6 以降、whencatch ステートメントで使用して、特定の例外ハンドラーを実行するために true でなければならない条件式を指定できます。式が false の場合、例外ハンドラーは実行されません。

public static async Task<string> MakeRequest()
{
    var client = new HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try
    {
        var responseText = await streamTask;
        return responseText;
    }
    catch (HttpRequestException e) when (e.Message.Contains("301"))
    {
        return "Site Moved";
    }
    catch (HttpRequestException e) when (e.Message.Contains("404"))
    {
        return "Page Not Found";
    }
    catch (HttpRequestException e)
    {
        return e.Message;
    }
}

自動プロパティ初期化子

C# 6 以降、自動プロパティに初期化値を指定して、型の既定値以外の値を使用できます。

public class DefaultValueOfProperty
{
    public string MyProperty { get; set; } = "Property";
}

式本体

C# 6 以降、メソッド、演算子、読み取り専用プロパティの式本体定義がサポートされ、C# 7.0 以降では、コンストラクター、ファイナライザー、プロパティ、インデクサーの式本体定義もサポートされています。

static void NewLine() => Console.WriteLine();

null 条件演算子

C# 6 以降、null 条件演算子が導入されました。null 条件演算子は、オペランドの評価結果が null 以外の場合のみ、メンバーアクセス ?. または要素アクセス ?[] をオペランドに適用し、それ以外の場合は null を返します。

// null 条件式
public class ConditionalNull
{
    event EventHandler AEvent;
    public void RaiseAEvent() => AEvent?.Invoke(this, EventArgs.Empty);
}

補間文字列

C# 6 以降、$ を使用して文字列内に式を挿入でき、コードの可読性が向上し、文字列連結のエラー発生確率が低下します。補間文字列内に中括弧を含める場合は、2 つの中括弧("{{" または "}}")を使用します。補間式で条件演算子を使用する必要がある場合は、括弧で囲む必要があります。C# 8 以降では、$@"..." または @$"..." の形式の補間逐語的文字列を使用できます。それ以前のバージョンでは、$@"..." 形式を使用する必要があります。

Console.WriteLine($"{name} is {age} year{(age == 1 ? "" : "s")} old.");

nameof

C# 6 では nameof 式が提供されています。nameof は変数、型、またはメンバーの名前(完全修飾ではない)を文字列定数として生成します。

public string Name
{
    get => name;
    set => name = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null");
}

out の改善

C# 7.0 では、out 構文が改善され、メソッド呼び出しの引数リスト内で直接 out 変数を宣言できるようになりました。個別の宣言文を記述する必要はなくなりました。

void Function(out int arg) { ... }
// 改善前
int n;
Function(out n);
// 改善後
Function(out int n);

タプル

C# 7.0 では、タプルに対する言語サポートが導入されました(以前のバージョンにもタプルはありましたが効率的ではありませんでした)。タプルを使用すると、複数のデータを含む単純な構造を表現でき、わざわざクラスや構造体を記述する必要がありません。タプルは値型であり、データメンバーを表す複数のパブリックフィールドを含む軽量なデータ構造で、メソッドを定義することはできません。C# 7.3 以降、タプルは ==!= をサポートします。

// 方法1:タプルフィールドの既定名を使用: Item1, Item2, Item3 など
(string, string) unnamedLetters = ("a", "b");
Console.WriteLine($"{unnamedLetters.Item1}, {unnamedLetters.Item2}");
// 方法2
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");
// 方法3
var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");
// 方法4:C# 7.1 以降、変数名の自動推論をサポート
int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // タプル要素名は "count" と "label"

メソッドがタプルを返す場合、タプルのメンバーを抽出するには、タプルの各値に対して個別の変数を宣言することで実現でき、これはタプルの分解と呼ばれます。タプルをメソッドの戻り値の型として使用すると、out メソッドパラメーターを定義する代わりになります。

// タプルの分解
var (first, last) = Range(numbers);
Console.WriteLine($"{first} to {last}");
(int max, int min) = Range(numbers);
Console.WriteLine($"{min} to {max}");

破棄

C# 7.0 以降、破棄がサポートされています。破棄はプレースホルダー変数であり、代入されていない変数と同等で、その変数を使用しないことを示します。アンダースコア _ で破棄変数を表します。以下に破棄の使用シナリオをいくつか示します。

// シナリオ1:タプル値の破棄
(_, _, area) = city.GetCityInformation(cityName);
// シナリオ2:C# 9 以降、ラムダ式のパラメーターを破棄可能
Func<int, int, int> constant = (_, _) => 42;
// シナリオ3:out パラメーターの破棄
DiscardsOut(out _);
static void DiscardsOut(out string s)
{
    s = "nothing";
    Console.WriteLine($"input is {s}");
}

パターンマッチング

C# 7.0 でパターンマッチング機能が追加され、以降の主要な C# バージョンごとにパターンマッチング機能が拡張されています。パターンマッチングは、式が特定の特性を持っているかどうかをテストするために使用されます。is 式、switch ステートメント、switch 式のいずれでもパターンマッチングをサポートしており、when キーワードを使用してパターンの追加ルールを指定できます。

パターンマッチングには現在、宣言パターン、型パターン、定数パターン、関係パターン、論理パターン、プロパティパターン、位置パターン、var パターン、破棄パターンが含まれます。詳細は公式ドキュメントを参照してください。

is パターン式は is 演算子の機能を改善し、1 つの命令で結果を割り当てることができます。

// is パターンマッチング
if (input is int count) do somthing... ;
// 従来の書き方
if (input is int)
{
    int count = (int)input;
    do somthing... ;
}
// is パターンによる null チェック
string? message = "This is not the null string";
if (message is not null) Console.WriteLine(message);

default リテラル式

既定値式は型の既定値を生成します。以前のバージョンでは default 演算子のみがサポートされていましたが、C# 7.1 以降、default 式の機能が強化され、コンパイラが式の型を推論できる場合、default を使用して型の既定値を生成できるようになりました。

// 新しい書き方
Func<string, bool> whereClause = default;
// 従来の書き方
Func<string, bool> whereClause = default(Func<string, bool>);

switch 式

C# 8 以降、switch 式を使用できます。switch 式は switch ステートメントに比べて次の点が改善されています。

  • 変数は switch キーワードの前に配置されます。
  • => を使用して case : 構造を置き換えます。
  • default 演算子の代わりに破棄 _ を使用します。
  • ステートメントの代わりに式を使用します。
public enum Level
{
    One,
    Two,
    Three
}
public static int LevelToScore(Level level) => level switch
{
    Level.One   => 1,
    Level.Two   => 5,
    Level.Three => 10,
    _ => throw new ArgumentOutOfRangeException(nameof(level), $"Not expected level value: {level}"),
};

using 宣言

C# 8 では using 宣言機能が追加されました。これは、宣言された変数をコードブロックの末尾で処理するようコンパイラに指示します。using 宣言は従来の using ステートメントよりもコードが簡潔で、どちらの書き方でもコードブロックの末尾で Dispose() が呼び出されます。

static void WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines.txt");
    do somthing... ;
    return;
    // file is disposed here
}

インデックスと範囲

C# 8 では、インデックスと範囲の機能が追加され、シーケンス内の単一の要素または範囲にアクセスするための簡潔な構文が提供されました。この構文は 2 つの新しい型と 2 つの新しい演算子に依存しています。

  • System.Index はシーケンスインデックスを表します。
  • System.Range はシーケンスの部分範囲を表します。
  • 末尾演算子 ^。この演算子と数字を使用して、末尾から何番目かを指定します。
  • 範囲演算子 ..。範囲の開始と末尾を指定します。

範囲演算子は範囲の開始を含み、範囲の末尾を含みません。

var words = new string[]
{               // 通常のインデックス         // インデックスに対応する末尾演算子
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (words.Length)    ^0
Console.WriteLine($"The last word is {words[^1]}"); // dog
var allWords = words[..]; // すべての値を含む。words[0..^0] と同じ
var firstPhrase = words[..4]; // 先頭から words[4] まで。words[4] は含まない
var lastPhrase = words[6..]; // words[6] から末尾まで
// 範囲変数の宣言
Range phrase = 1..4;
var text = words[phrase];

?? と ??=

?? 結合演算子:C# 6 以降で使用可能。左オペランドの値が null でない場合はその値を返し、それ以外の場合は右オペランドを評価してその結果を返します。左オペランドの評価結果が null 以外の場合、右オペランドは評価されません。

??= 結合代入演算子:C# 8 以降で使用可能。左オペランドの評価結果が null の場合のみ、右オペランドの値を左オペランドに代入します。それ以外の場合は右オペランドは評価されません。??= 演算子の左オペランドは、変数、プロパティ、またはインデクサー要素である必要があります。

// ?? 結合演算子
Console.WriteLine($"name is {OutputName(null)}");
static string OutputName(string name) => name ?? "some one";
// ??= 代入演算子の使用
variable ??= expression;
// 従来の書き方
if (variable is null)
{
    variable = expression;
}

トップレベルステートメント

C# 9 ではトップレベルステートメントが導入されました。これにより、アプリケーションから不要な定型コードが削除され、アプリケーション内の 1 つのファイルのみがトップレベルステートメントを使用できます。トップレベルステートメントにより、メインプログラムが読みやすくなり、不要なパターン(名前空間、class Programstatic void Main())が削減されます。

VS でコマンドラインプロジェクトを作成し、.NET 5 以上を選択すると、トップレベルステートメントが使用されます。

// VS2022 で .NET 6.0 プラットフォームのコマンドラインプログラムを作成するとデフォルトで生成される内容
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

global using

C# 10 では global using ディレクティブが追加されました。キーワード globalusing ディレクティブの前にある場合、その using はプロジェクト全体に適用され、各ファイルの using ディレクティブの行数を減らすことができます。global using ディレクティブは任意のソースコードファイルの先頭に記述できますが、非グローバル using の前に配置する必要があります。

global 修飾子は static 修飾子と一緒に使用でき、using エイリアスディレクティブにも適用できます。どちらの場合も、ディレクティブのスコープは現在のコンパイル内のすべてのファイルです。

global using System;
global using static System.Console; // グローバル静的インポート
global using Env = System.Environment; // グローバルエイリアス

ファイルスコープの名前空間

C# 10 ではファイルスコープの名前空間が導入されました。名前空間をステートメントとして含め、セミコロンで終了し、中括弧は不要です。通常、1 つのコードファイルには 1 つの名前空間のみが含まれるため、これによりコードが簡素化され、1 レベルの入れ子が削除されます。ファイルスコープの名前空間は、入れ子になった名前空間や 2 つ目のファイルスコープの名前空間を宣言することはできません。また、型を宣言する前に記述する必要があり、そのファイル内のすべての型はその名前空間に属します。

using System;
namespace SampleFileScopedNamespace;
class SampleClass { }
interface ISampleInterface { }
struct SampleStruct { }
enum SampleEnum { a, b }
delegate void SampleDelegate(int i);

with 式

C# 9 以降、with 式が導入されました。これは、変更された特定のプロパティとフィールドを使用して操作対象のオブジェクトのコピーを生成し、変更されていない値は元のオブジェクトと同じ値を保持します。参照型メンバーの場合、オペランドのコピー時にはそのメンバーインスタンスへの参照のみがコピーされるため、with 式で生成されたコピーと元のオブジェクトは同じ参照型インスタンスにアクセスできます。

C# 9 では、with 式の左オペランドは record 型である必要がありますが、C# 10 では改善され、with 式の左オペランドに struct 型も使用できるようになりました。

public record NamedPoint(string Name, int X, int Y);
var p1 = new NamedPoint("A", 0, 0);
var p2 = p1 with { Name = "B", X = 5 };
さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む