Properties are the controlled access points to a class’s data. They look like fields at the call site (post.Title = "Hello") but are actually methods under the hood — the compiler generates a get_Title() and set_Title() method pair. This means you can add validation, transformation, computation, or notifications to a property at any time without changing any code that reads or writes it. Properties are what make C# classes both convenient and robust.
Auto-Implemented Properties
public class User
{
// Auto-property — compiler generates a hidden backing field
public int Id { get; set; }
public string Name { get; set; } = string.Empty; // default value
public string Email { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
// Read-only auto-property — can only be set in constructor or initialiser
public DateTime CreatedAt { get; } = DateTime.UtcNow;
// Init-only property (C# 9+) — settable in object initialiser, then immutable
public string Role { get; init; } = "User";
}
Note: The
init accessor (C# 9+) allows a property to be set only during object initialisation — either in the constructor or in an object initialiser expression (new User { Role = "Admin" }). After the object is constructed, the property is read-only. This is ideal for DTOs and domain entities where the value should be set once and never changed. Entity Framework Core fully supports init properties for mapping.Tip: Always initialise string properties to
string.Empty (or a sensible default) in the property declaration: public string Name { get; set; } = string.Empty. With <Nullable>enable</Nullable> in the .csproj, a non-nullable string property without an initialiser or constructor assignment causes a compiler warning. Initialising to string.Empty satisfies the compiler, documents your intent, and prevents null-reference errors from unset properties in model binding.Warning: Entity Framework Core uses properties (not fields) to map columns. If a property has a private setter (
{ get; private set; }), EF Core can still set it via reflection during materialisation — but the property must exist and be a real property, not a field. If you want full EF Core compatibility with private setters, use the protected modifier instead of private, or use the init accessor which EF Core supports natively from EF 7+.Properties with Custom Logic
public class Product
{
private string _name = string.Empty;
private decimal _price;
// Custom get and set — validation in setter
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Product name cannot be empty.");
_name = value.Trim();
}
}
// Price with validation
public decimal Price
{
get => _price;
set => _price = value >= 0
? value
: throw new ArgumentOutOfRangeException(nameof(value), "Price cannot be negative.");
}
// Computed property — no backing field needed
public decimal PriceWithVat => _price * 1.20m; // 20% VAT
// Private setter — external code can read but not write directly
public int StockLevel { get; private set; }
public void AddStock(int quantity)
{
if (quantity <= 0) throw new ArgumentException("Quantity must be positive.");
StockLevel += quantity;
}
}
Object Initialisers
// Object initialiser syntax — set properties after construction
var user = new User
{
Id = 1,
Name = "Alice",
Email = "alice@example.com",
Role = "Admin", // init-only property — must use object initialiser
};
// Equivalent to:
// var user = new User();
// user.Id = 1;
// user.Name = "Alice";
// user.Email = "alice@example.com";
// user.Role = "Admin"; ← can't do this after init — init-only!
// Used throughout ASP.NET Core for DTO construction and test data setup
Common Mistakes
Mistake 1 — Not initialising string properties (nullable warning or null ref)
❌ Wrong — compiler warning with nullable enabled; possible NullReferenceException:
public class User { public string Name { get; set; } } // warning: not initialised
✅ Correct:
public class User { public string Name { get; set; } = string.Empty; }
Mistake 2 — Using a public field when a property is needed
❌ Wrong — model binding and EF Core do not work with public fields:
public string Title; // field — no get/set
✅ Correct:
public string Title { get; set; } = string.Empty; // property — works everywhere