Back to Blog
C#

C# Field Usage: Declaration, Initialization, and Access

c# field usage: Learn how to declare, initialize, and access fields in C#, including readonly and static modifiers, and when to choose fields over properties.

C# fieldsfield initializationreadonly fieldsstatic fieldsC# class design
Abstract representation of a C# class with fields highlighted, showing structured data storage.

Fields in C# are the backbone of state within classes and structs. Understanding correct c# field usage is essential for writing predictable, maintainable code. A field is a variable declared directly inside a type, and it holds the data that instances (or the type itself) maintain. This article covers declaration, initialization, default values, readonly and static variants, and the practical decisions around using fields versus properties.

Declaring Fields and Their Default Values

The simplest field declaration appears inside a class or struct, outside any method. A field can be public, private, protected, internal, or combinations such as protected internal and private protected. When a field is declared, it automatically receives a default value even before any constructor runs: reference types get null, numeric types get zero, bool gets false, char gets '\0', and enum types get the value of 0 even if no member corresponds.

public class Order { public int Id; // default: 0 public string? Note; // default: null public decimal Total; // default: 0m }

The default value assignment happens before any constructor body executes. This behavior differs from local variables, which C# requires to be definitely assigned before use. Fields do not have that requirement because the runtime zeroes the memory allocation.

Explicit Initialization at Declaration

You can assign an initial value directly at the declaration point. This runs when an instance is constructed, just before the constructor body executes. Using initializers is often clearer than assigning values inside every constructor, because it keeps related state close together.

public class Logger { private string _outputPath = "logs/default.log"; private int _maxFileSize = 1024 * 1024; }

Initializers run in the order they appear in the type declaration. That matters when one field initializer references another field declared earlier. Referencing a later field from an earlier initializer produces a warning because the referenced field is still at its default value.

For static fields, the order matters even more. Static initializers run before the first access to the type, and before any static constructor. The C# compiler executes static field initializers in textual order. If a static initializer calls a method that accesses a static field declared later, the later field is still default-initialized.

Choosing Between Constructor Assignment and Field Initializers

Both approaches produce the same observable result in most cases, but the tradeoff appears when constructors take parameters. Field initializers cannot use constructor parameters, so any field that depends on a parameter must be assigned inside the constructor.

public class FileHandler { private readonly string _fullPath; public FileHandler(string baseDirectory, string fileName) { _fullPath = Path.Combine(baseDirectory, fileName); } }

When every constructor assigns the same value, a field initializer removes duplication. When the value differs by constructor, the initializer forces you to repeat assignment or ignore the parameter, which is a design smell. In general, prefer initializers for constants or defaults that never vary, and constructor assignment for values that depend on construction parameters.

Readonly Fields: Immutable After Construction

A readonly field can be assigned only at declaration or in a constructor. After the constructor completes, attempts to assign it produce a compile-time error. This provides a stronger guarantee than a non-readonly field and makes the intent clear to other developers and to the compiler.

public class Settings { public readonly string ConfiguredName; public Settings(string name) { ConfiguredName = name; } } ```n Readonly fields are not the same as constants (`const`). A `const` field is a compile-time literal; it is embedded directly into referencing code at build time. A `readonly` field is evaluated at runtime and can hold any value, including instance-dependent ones. Use `const` only for values that never change and that are known at compile time, such as `int MaxRetry = 5`. Use `readonly` for values fixed at runtime, such as a connection string read from configuration. Another subtlety is that a `readonly` field of a reference type only makes the reference immutable, not the object it refers to. You can still modify the contents of an array or a mutable object reached through a `readonly` field. If true deep immutability is required, use immutable collections or expose only read-only views. ## Static Fields and the Type State A `static` field belongs to the type itself, not to any instance. All instances share the same static field value. Static fields are often used for caches, configuration, or service locators. However, they introduce global mutable state, so they require discipline. ```csharp public class Cache { private static Dictionary<string, string> _entries = new(); public static void Add(string key, string value) { _entries[key] = value; } }

Thread safety is a real concern here. A static field is accessible from multiple threads concurrently. Without synchronization, reads and writes can race. The simplest approach is to lock around all access, or use a thread-safe collection such as ConcurrentDictionary. A static readonly field is initialized once and is safe if the referenced object is not mutated afterward.

Static fields are also a source of hidden coupling. Any code that writes to a static field changes the observable state for the entire process. That can create difficult-to-reproduce bugs, especially in tests where static state leaks across test cases. Use static fields sparingly, and prefer dependency injection or explicit configuration objects when the state can be passed through constructors.

Field vs Property: Which One Should You Use?

While fields are the raw storage location, properties are accessors that control how field values are read and written. In modern C#, properties are the preferred way to expose state to external code because they allow you to add validation, lazy loading, change notification, or custom getter/setter logic later without changing the public API.

AspectFieldProperty
Data storageYesYes, usually via a backing field
ValidationCannot addCan add in set accessor
Change eventsNot possiblePossible via INotifyPropertyChanged
SerializationWorks directlyWorks with care
OverridableNoYes (virtual)
Access controlLimited (single modifier)Full accessor control
CompatibilityCan change to property laterCan change to field later

The key decision point is whether the field is exposed outside the class. If the field is truly internal implementation detail, a private field is fine. If any external code reads or writes the value, use a property. A public field fixes the storage location into the public API; changing it to a property later is a breaking change. Properties give you a backup plan: you can later change the backing storage without affecting consumers.

For data-only classes, properties with auto-implemented getters and setters are nearly identical to fields in syntax and readability, but they provide better flexibility. Use fields only for internal implementation where property semantics would add no value.

Production Considerations for Field Usage

In production code, the visibility and mutability of fields directly affect maintainability and thread safety. A class full of public mutable fields is hard to reason about because any caller can change any value. Prefer making fields private and exposing them only through methods or properties.

Another concern is serialization. Many serializers, such as System.Text.Json, include public properties but not private fields by default. A public field will be serialized, but relying on that is risky because changing the field to a property later will silently alter the serialized output. Use properties for serializable members.

When you use readonly fields in a meaningful way, the compiler enforces immutability, which can prevent accidental assignment bugs. However, readonly does not protect against mutation of the referenced object, so document that limitation and prefer immutable collections when the data must not change.

Performance considerations are usually minor. Field access is direct memory access, while property access adds a method call unless the JIT inlines it. In practice, properties are inlined again and again, so the measurable difference is negligible. Prefer readability over micro-optimizations. If you have a hot path and profiling shows property access is a bottleneck, adding auto-properties is fine because the JIT handles them well.

Finally, be consistent. If a type uses properties for all state, don't suddenly introduce a public field for one value. Consistency helps the next developer understand the design intent and reduces the chance of unexpected behavior.

Advanced: Field Initialization in Structs and Default Constructor Changes

Historically, structs did not allow parameterless constructors, but C# 10 permits explicit parameterless struct constructors. This change affects field initializers. If you define a field initializer in a struct without a constructor, the initializer now runs in the parameterless constructor. If you do not define a constructor, the fields are still zero-initialized. The exact behavior is best understood by reading the language specification for your C# version, because the rules changed.

This matters for production code because structs are often used in arrays and high-performance scenarios. If you rely on a parameterless struct constructor to initialize fields, be aware that it may not run in every context, such as when a struct is default-created via default(T) or when an array is allocated. In those cases, all fields are zero-initialized. The default value of a struct is always the zeroed layout, regardless of whether a parameterless constructor exists.

public struct Point { public int X; public int Y; } // Creates a zeroed struct Point p = default;

For fields that must always start with a non-zero value, such as an Id counter or a flag, relying on explicit initialization in a constructor is safer than depending on a parameterless constructor that might not run. Always code with the understanding that default(T) gives you zeroed storage, and any invariants must be restored by the code that creates the value.

When you design with fields, keep that in mind: fields in structs are the only way to store state, and their default initialization is by zeroing. If you need a fallback sentinel value, store it in a static readonly field and compare against it after reading the struct field.

Field usage in C# is straightforward but requires attention to initialization order, thread safety, and the public API boundary. Choose the right modifier, use properties for exposed state, and understand the default-value semantics of your types.

c# field usage: Practical Usage and Code Examples | RYUSLOG DEV