0Pricing
C# Academy · レッスン

initとwithによる不変性

init専用setterで不変オブジェクトを作成し、with式で破壊的変更を行わずにコピーを生成します。

「initとwithによる不変性」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。

不変性とは

不変オブジェクトは、作成後に変更できません。不変性により、意図しない変更を防ぎ、オブジェクトは既定でスレッドセーフになり、コードの理解も容易になります。C# には、不変性を扱いやすくするための init セッターと with 式が用意されています。

init 専用セッター

init アクセサーを使用すると、オブジェクトの初期化時(コンストラクターまたはオブジェクト初期化子)にプロパティを設定できますが、その後は設定できません。これは set に相当する不変版です。

public class Point
{
    public double X { get; init; }
    public double Y { get; init; }
}

var p = new Point { X = 3.0, Y = 4.0 }; // OK — init phase
p.X = 5.0; // COMPILE ERROR — cannot set after init

// All of these are valid init-phase assignments:
var p2 = new Point(X: 1.0, Y: 2.0);
var p3 = new Point { X = 0, Y = 0 };

レコードの位置指定プロパティにおける init

位置指定レコードのプロパティは、既定で init 専用です。これによってレコードが不変になります。コンパイラーは各位置指定パラメーターに対して get; init; を生成します。

// This record:
public record Person(string Name, int Age);

// Is equivalent to:
public record Person
{
    public string Name { get; init; }
    public int Age    { get; init; }
    public Person(string Name, int Age) { this.Name = Name; this.Age = Age; }
    // + Deconstruct, Equals, GetHashCode, ToString
}

var alice = new Person("Alice", 30);
alice.Name = "Bob"; // COMPILE ERROR

with 式: 非破壊的な変更

with 式は、特定のプロパティを変更したレコードのコピーを作成します。元のレコードは変更されないため、真の不変性が保たれます。

public record Person(string Name, int Age, string Email);

var alice = new Person("Alice", 30, "alice@example.com");

// Create a modified copy — alice is unchanged
var olderAlice = alice with { Age = 31 };
var renamed    = alice with { Name = "Alicia", Email = "alicia@example.com" };

Console.WriteLine(alice.Name);      // Alice (unchanged)
Console.WriteLine(olderAlice.Age);  // 31
Console.WriteLine(renamed.Name);    // Alicia

レコード以外の型での with

C# 10 では、コピーコンストラクターのセマンティクスを持つ任意の構造体やクラスで with を使用できます。ただし、最も自然に使えるのはレコードです。クラスでは手動で実装する必要があります。

// struct with with-expression:
public struct Temperature
{
    public double Celsius { get; init; }
    public double Fahrenheit => Celsius * 9 / 5 + 32;
}

var t1 = new Temperature { Celsius = 20 };
var t2 = t1 with { Celsius = 25 }; // copy with change
Console.WriteLine(t1.Celsius); // 20 — unchanged
Console.WriteLine(t2.Celsius); // 25

with 式のチェーン

複数の with 式をチェーンして複雑な変換を組み立てられます。各ステップで、直前の値から新しい不変値が作成されます。

public record Order(int Id, string Status, decimal Total, DateTime UpdatedAt);

var order = new Order(42, "Pending", 99.99m, DateTime.UtcNow);

// Apply a promotion discount then mark as confirmed
var finalOrder = order
    with { Total    = order.Total * 0.9m }    // 10% off
    with { Status   = "Confirmed" }
    with { UpdatedAt = DateTime.UtcNow };

Console.WriteLine(order.Status);     // Pending (original unchanged)
Console.WriteLine(finalOrder.Status); // Confirmed

不変型の計算プロパティ

不変型の派生プロパティは、自然に純粋なものになります。固定されたプロパティ値から計算され、同じ入力に対して常に同じ結果を返します。

public record Money(decimal Amount, string Currency)
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Currency mismatch");
        return this with { Amount = Amount + other.Amount };
    }

    public Money Multiply(decimal factor) =>
        this with { Amount = Amount * factor };

    public override string ToString() =>
        $"{Amount:F2} {Currency}";
}

var price = new Money(10.00m, "USD");
var tax   = price.Multiply(0.08m);
var total = price.Add(tax);
Console.WriteLine(total); // 10.80 USD

不変コレクション

レコードを ImmutableList<T> や System.Collections.Immutable のその他の型と組み合わせることで、完全に不変なデータ構造を作成できます。

using System.Collections.Immutable;

public record ShoppingCart(
    string UserId,
    ImmutableList<CartItem> Items)
{
    public ShoppingCart AddItem(CartItem item) =>
        this with { Items = Items.Add(item) };

    public ShoppingCart RemoveItem(int productId) =>
        this with { Items = Items.RemoveAll(i => i.ProductId == productId) };

    public decimal Total => Items.Sum(i => i.Price * i.Quantity);
}

不変性によるスレッドセーフ

不変オブジェクトは本質的にスレッドセーフです。構築後に状態が変化しないため、複数のスレッド間で共有する際に同期処理は必要ありません。

// Immutable config record shared across all threads safely
public record AppConfig(
    string ConnectionString,
    int MaxRetries,
    TimeSpan Timeout);

// Register as singleton — safe because record is immutable
builder.Services.AddSingleton(
    new AppConfig(
        ConnectionString: config["DB"]!,
        MaxRetries: 3,
        Timeout: TimeSpan.FromSeconds(30)));

// Any thread can read this simultaneously without locks

実践例: 関数型イベントソーシング

不変レコードはイベントソーシングと自然に組み合わせられます。各ドメインイベントは不変であり、イベントを畳み込むことで状態を導出するため、変更による予期しない問題がありません。

public record OrderState(
    int Id,
    string Status = "Draft",
    decimal Total = 0m);

public static OrderState Apply(OrderState state, object evt) => evt switch
{
    OrderPlaced   e => state with { Status = "Pending", Total = e.Total },
    OrderShipped  _ => state with { Status = "Shipped" },
    OrderCancelled _ => state with { Status = "Cancelled" },
    _ => state
};

// Fold events to get current state:
var state = events.Aggregate(
    new OrderState(id),
    Apply);

クイックチェック

'with' 式は元のレコードに対して何を行いますか。

まとめ: init と with による不変性

重要なポイント:

  • init アクセサー: 初期化中のみ設定でき、その後は設定できません
  • レコードの位置指定プロパティは、既定で init 専用です
  • with 式: 変更したコピーを作成します。元の値は変更されません
  • 複数段階の変換には with 式をチェーンして使用します
  • 不変型は同期処理なしでスレッドセーフになります
  • ImmutableList<T> と組み合わせて、完全に不変なオブジェクトグラフを作成できます

よくある質問

「initとwithによる不変性」レッスンは無料ですか?

はい。「initとwithによる不変性」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「initとwithによる不変性」で何を学びますか?

init専用setterで不変オブジェクトを作成し、with式で破壊的変更を行わずにコピーを生成します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「initとwithによる不変性」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Record型:基礎と構文
  2. initとwithによる不変性
  3. 値の等価性と分解
  4. ドメイン駆動設計におけるRecord
← C# Academyに戻る