Back to Blog
C#

C# Member Variable Usage: Fields, Access, and Initialization

c# member variable usage: Learn how to declare and use member variables in C# classes, including access modifiers, static vs instance, readonly, and properties.

C# fieldsmember variablesclass designaccess modifiersstatic vs instancereadonly const
C# class diagram showing fields and properties with access modifiers

In C#, member variables—also called fields—store the state of a class or struct. They are the building blocks of object-oriented design, and how you declare and manage them affects encapsulation, memory usage, and thread safety. This article covers the practical aspects of c# member variable usage, from basic field declarations to the tradeoffs between fields and properties.

Declaring Fields: Syntax and Access Modifiers

A field is declared inside a type with a type and a name. The access modifier controls which parts of the code can read or write it. Common modifiers are public, private, protected, internal, and protected internal.

public class Customer { public string Name; private int _age; protected string _email; }

Public fields expose the internal state directly, which makes it easy for any code to change the value without validation or notification. In practice, public fields are rarely appropriate because they break encapsulation. Private fields are the common choice, and properties expose them in a controlled way. The protected modifier allows derived classes to access the field, which can be useful for base-class logic but also creates coupling between base and derived types.

Instance vs Static Fields

Instance fields belong to each object; static fields belong to the type itself. Consider this example:

public class Counter { public int InstanceCount; // each instance has its own copy public static int TotalCount; // shared across all instances }

Every Counter object has its own InstanceCount. Changing it on one object does not affect another. TotalCount, on the other hand, exists once for the entire Counter type. You can access it without creating an instance: Counter.TotalCount. Static fields are useful for shared configuration, caches, or counters that track cross-instance state. They also persist for the lifetime of the type, so they consume memory even if no instances exist. Because they are shared, they require careful synchronization when multiple threads read or write them.

Readonly and Const Fields

const fields are compile-time constants and are implicitly static. readonly fields can be assigned only at declaration or in a constructor. The distinction matters for values that are known at compile time versus values that depend on runtime data.

public class Settings { public const int MaxRetries = 3; public readonly string ConnectionString; public Settings(string connectionString) { ConnectionString = connectionString; } }

const requires a compile-time constant expression, such as a numeric literal or a string literal. The compiler substitutes the value wherever the constant is used, so no field storage exists at runtime. readonly can hold any value, including one computed at runtime, and it is stored as a real field. Use const for values that will never change and are known at compile time, such as configuration limits or mathematical constants. Use readonly for values that are fixed after construction but may vary between instances, like a connection string passed to the constructor.

Field Initialization and Constructors

Fields can be initialized inline or in a constructor. Inline initializers run before the constructor body, in the order they appear. If you do not initialize a field, it gets a default value: reference types default to null, numeric value types to zero, and bool to false.

public class Example { public int Number = 5; // inline initialization public string Text; // default null public Example() { Text = "default"; // constructor assignment } }

Inline initialization is convenient for simple defaults. For more complex logic, such as deriving a value from constructor parameters or performing validation, assign in the constructor. Be aware of the order: inline initializers run first, so if a constructor also assigns the field, the constructor value overwrites the inline value. This is not a problem if you are intentional about the final state.

Properties vs Fields: Encapsulation

Properties provide controlled access to fields. They allow you to add validation, lazy loading, or change notification without changing the public contract. A typical pattern is a private field with a public property:

private int _score; public int Score { get { return _score; } set { if (value >= 0) _score = value; } }

Properties are more than syntactic sugar; they let you change the implementation later without breaking callers. For example, you can add a set that raises an event or logs the change. Auto-properties give you a concise way to define a property with a hidden backing field:

public int Score { get; set; }

Use properties for any field that is part of the public API. Direct field access is acceptable for private implementation details that do not need validation or notification. In performance-sensitive code, direct field access is slightly faster because it avoids a method call, but the JIT often inlines simple property getters and setters, so the difference is usually negligible.

Naming Conventions and Maintainability

Consistent naming makes field usage predictable. The common C# convention is to use camelCase for private fields, often with an underscore prefix (_fieldName). Public fields, when they exist, use PascalCase. Since public fields are rare, you will most often see private fields with underscores.

public class Order { private int _totalAmount; private string _customerName; }

A clear naming scheme helps distinguish fields from local variables and parameters. It also makes it easier to spot fields that should be readonly. If a field is never reassigned after construction, mark it readonly. This communicates intent and prevents accidental assignment later. It also helps the compiler and runtime with certain optimizations, though the main benefit is maintainability.

Concurrency and Thread Safety

Mutable instance fields are not thread-safe by default. If multiple threads read and write the same field without synchronization, you can get race conditions. Static fields are even more exposed because they are shared across all threads. For example, a simple counter implemented with a plain field can lose updates under concurrency.

public class SharedCounter { private int _count; public void Increment() { Interlocked.Increment(ref _count); } }

Using Interlocked or a lock statement ensures atomicity. Alternatively, you can design fields as immutable—assign once and never change—which eliminates the need for synchronization. For instance, readonly fields that are set in the constructor are safe to read from multiple threads once the object is fully constructed. When you expose fields through properties, you can add synchronization inside the property accessors, but that adds complexity. Consider whether the field truly needs to be mutable and shared before deciding how to protect it.

Performance and Memory Considerations

Fields directly contribute to an object's memory footprint. Reference-type fields add a reference (8 bytes on a 64-bit system), while value-type fields are stored inline. Static fields do not increase per-object size, but they live for the entire type lifetime. Accessing a field is fast—it is a direct memory read or write. Properties add a method call, but the JIT often inlines simple getters and setters, so the overhead is minimal in practice.

When you have many objects, the number and size of fields matter. For example, a class with several int fields uses more memory than one with a single int[]. If memory is a concern, consider using structs for small, value-based types, but be aware of copying behavior. Also, be careful with static collections that grow without bound; they can cause memory leaks because they are rooted for the type's lifetime. Prefer readonly for fields that should not change after construction; this reduces the chance of accidental mutation and makes the object's state more predictable.

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