Init-Only Properties in C#

Vaibhav • September 11, 2025

In the previous article, we explored record types - a concise way to define immutable reference types with value-based equality. One of the key features that made records so powerful was their use of init-only properties. But this feature isn’t limited to records - you can use it in regular classes too. In this article, we’ll unpack what init-only properties are, why they matter, and how they help you write safer, more maintainable code.

We’ll also explore how init-only properties fit into the broader theme of immutability, how they differ from traditional setters, and how to use them effectively in your own types - whether you’re building data models, configuration objects, or APIs.

What Are Init-Only Properties?

Init-only properties are a feature introduced in C# 9.0 that allow you to set a property only during object initialization - either via an object initializer or a constructor. After that, the property becomes read-only.

public class User
{
    public string Name { get; init; }
    public int Age { get; init; }
}

This looks similar to auto-properties you’ve seen before, but the keyword init replaces set. That small change has big implications: you can’t modify these properties after the object is created.

Init-only properties are part of the language’s push toward safer, more predictable code. They help enforce immutability without requiring complex constructor logic.

Setting Init-Only Properties

You can set init-only properties using object initializer syntax:

var user = new User
{
    Name = "Vaibhav",
    Age = 30
};

This works because the assignment happens during initialization. If you try to change the property later, the compiler will stop you:

user.Name = "Vikas"; // ❌ Error: cannot assign to init-only property

This ensures that once the object is constructed, its state remains stable - a key principle in functional and concurrent programming.

Using Init-Only Properties with Constructors

You can also set init-only properties inside a constructor:

public class Product
{
    public string Name { get; init; }
    public decimal Price { get; init; }

    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }
}

This gives you flexibility: you can use either object initializers or constructors depending on your design goals.

Use constructors when you want to enforce required values. Use object initializers when you want flexibility and clarity.

Why Init-Only Properties Matter

Traditional properties with set allow changes at any time. That’s convenient, but it can lead to bugs - especially when objects are shared across threads or reused in unexpected ways. Init-only properties solve this by making the object’s state immutable after creation.

This immutability has several benefits:

  • It makes your code easier to reason about.
  • It prevents accidental changes.
  • It improves thread safety.
  • It aligns with modern design principles like functional programming and value semantics.

Init-Only vs Read-Only Properties

You might wonder how init-only properties differ from read-only properties. Here’s a quick comparison:

public class Config
{
    public string Path { get; } // Read-only
    public string Mode { get; init; } // Init-only

    public Config(string path)
    {
        Path = path; // ✅ Allowed in constructor
    }
}

Read-only properties can only be set in the constructor. Init-only properties can be set in the constructor or via object initializers. That makes them more flexible - especially for types with many optional properties.

Note: Init-only properties are not a replacement for read-only properties. Use each where appropriate: read-only for strict constructor-only values, init-only for flexible initialization.

Combining Init-Only Properties with Records

In the previous article, we saw how records use init-only properties by default. Here’s a quick refresher:

public record Person(string Name, int Age);

This defines two init-only properties. You can use object initializers or positional parameters:

var p1 = new Person("Vaibhav", 30);
var p2 = p1 with { Age = 31 };

The with-expression creates a new record with updated values - without modifying the original. This is possible because of init-only properties.

Init-Only Properties in APIs and DTOs

Init-only properties are especially useful in APIs and data transfer objects (DTOs). They let you define types that are easy to construct but hard to mutate - which is ideal for serialization, validation, and versioning.

public class OrderDto
{
    public int Id { get; init; }
    public string Customer { get; init; }
    public DateTime Date { get; init; }
}

This type can be deserialized from JSON, validated, and passed around safely - without worrying about accidental changes.

Init-Only Properties and Object Initializers

Object initializers are a natural fit for init-only properties. They let you set multiple values in a clean, readable way:

var config = new Config
{
    Path = "/data",
    Mode = "readonly"
};

This is especially helpful when working with configuration objects, UI models, or test data.

Limitations of Init-Only Properties

Init-only properties are powerful, but they have some limitations:

  • You can’t modify them after initialization - even with reflection.
  • They don’t work with older versions of C# or .NET.
  • They require compiler support - not just runtime support.

These limitations are usually minor, but it’s good to be aware of them - especially when working in mixed environments or legacy codebases.

Common Mistakes and How to Avoid Them

Here are a few common mistakes developers make with init-only properties:

  • Trying to assign values after initialization - which causes compiler errors.
  • Using set instead of init - which allows unwanted mutations.
  • Assuming init-only properties are thread-safe - they’re not unless the object itself is immutable.
  • Mixing mutable and immutable properties - which can lead to confusing APIs.

Init-only properties are enforced by the compiler using a special metadata flag. This means even advanced tools like reflection can’t bypass the restriction.

Design Tips for Using Init-Only Properties

Init-only properties are a design tool. Use them to express intent clearly:

  • Use init for properties that should never change after creation.
  • Use set for properties that need to be updated.
  • Use readonly fields or get-only properties for strict immutability.
  • Use object initializers for clarity and flexibility.

These choices help you build APIs that are predictable, safe, and easy to use.

Summary

Init-only properties are a modern feature in C# that help enforce immutability and improve code safety. They allow you to set values during object creation - but prevent changes afterward. You’ve learned how to define init-only properties, use them with object initializers and constructors, combine them with records, and apply them in real-world scenarios like APIs and DTOs.

By using init-only properties, you make your code easier to reason about, safer to use, and more aligned with modern design principles. In the next article, we’ll explore Top-Level Statements - a feature that simplifies your program’s entry point and makes your code more concise.