Back to Blog
C#

C# Getter Setter: Properties in C#

c# getter setter: Learn how to use getter and setter accessors in C# properties, including auto-properties, validation, and performance considerations.

C#PropertiesAccessorsAuto-PropertiesEncapsulation
Diagram showing a C# property with get and set accessors controlling access to a private field.

c# getter setter requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

In C#, the getter and setter accessors define how a property is read and written. A property is a member that provides a flexible mechanism to read, write, or compute the value of a private field. The simplest declaration looks like this:

public class Person { private string _name; public string Name { get { return _name; } set { _name = value; } } }

The get accessor returns the field value; the set accessor assigns the incoming value to the field. This is the foundation of the C# getter setter pattern. Properties are not just syntactic sugar—they give you control over how data is accessed and modified, and they participate in the type's public contract.

Declaring Properties with Get and Set

The full property syntax is explicit: you define a backing field, a get block, and a set block. Each block can contain any valid C# statements, not just a simple return or assignment. For example, you can add logging, validation, or lazy initialization.

public class Temperature { private double _celsius; public double Celsius { get { return _celsius; } set { if (value < -273.15) throw new ArgumentOutOfRangeException(nameof(value)); _celsius = value; } } public double Fahrenheit { get { return _celsius * 9 / 5 + 32; } } }

Here, the Celsius setter validates the input before storing it, and Fahrenheit is a read-only computed property with only a get accessor. You can also make a property write-only by providing only a set accessor, though that is rarely useful. The value keyword inside the setter always refers to the value being assigned.

Auto-Properties and Their Limitations

When you do not need custom logic, C# provides auto-properties. The compiler generates a hidden backing field for you.

public class Person { public string Name { get; set; } public int Age { get; set; } }

Auto-properties are concise and are the default choice for simple data containers. They are equivalent to writing the full property with a private backing field, but you cannot access the backing field directly. If you later need validation or a computed value, you must convert the auto-property to a full property, which is a source-compatible change for callers.

You can also use auto-property initializers to set an initial value without a constructor:

public class Person { public string Name { get; set; } = "Unknown"; }

Auto-properties support get-only and init-only accessors. The init accessor allows assignment only during object initialization, which is useful for immutable types.

Expression-Bodied Members for Properties

For read-only properties that return a computed value, you can use expression-bodied syntax. This reduces boilerplate for simple expressions.

public class Rectangle { public double Width { get; set; } public double Height { get; set; } public double Area => Width * Height; }

The => syntax is equivalent to get { return Width * Height; }. You can also use expression-bodied getters and setters for full properties, but that is less common because setters usually contain multiple statements. Expression-bodied members are best for single-expression accessors.

Controlling Access with Modifiers and Validation

Properties can have different access modifiers on the getter and setter. For example, you might want a public getter but a private setter to prevent external modification.

public class Counter { public int Count { get; private set; } public void Increment() { Count++; } }

Here, Count can be read from outside but only changed from within the class. This is a common pattern for encapsulated state. You can also use protected, internal, or protected internal on one accessor, but the more restrictive accessor's modifier must be specified.

Validation logic often lives inside the setter, as shown earlier. This centralizes rules and prevents invalid states from ever being assigned. However, be careful not to throw exceptions from setters in performance-critical paths or in situations where the caller does not expect them. Consider using a method like SetName if the operation is complex or asynchronous.

Performance and Runtime Considerations

Properties are method calls under the hood. The JIT compiler often inlines simple getters and setters, so the overhead is negligible in most cases. However, there are situations where property access can be slower than direct field access:

  • Virtual properties: If a property is virtual, the call cannot be inlined because the runtime must dispatch to the derived implementation.
  • Properties that perform heavy computation or I/O in the getter.
  • Properties used in tight loops where the getter is called millions of times and contains non-trivial logic.

In practice, you should not replace properties with public fields for performance reasons unless profiling shows a measurable bottleneck. The maintainability and encapsulation benefits of properties far outweigh the micro-optimization. If you need a field-like API and the property is trivial, the JIT will likely inline it.

One runtime detail: auto-properties use a compiler-generated backing field. The field is not accessible to you, but reflection can see it. This matters if you use serialization frameworks that rely on field names or if you need to bypass the property logic for some reason.

Choosing Between Fields and Properties

Public fields and properties are not interchangeable in the public API. Fields are data storage; properties are methods that expose data. Changing a public field to a property is a binary-breaking change for code compiled against the original type, even if the source code looks the same. This is why most .NET design guidelines recommend using properties for all public data.

Use a public field only when:

  • The value is a constant or a static readonly that is truly immutable.
  • The type is internal and you control all usage.
  • You are interop with a C-style API that requires fields.

In all other cases, prefer a property. Properties allow you to add validation, change the backing storage, or implement lazy loading without breaking callers. They also participate in data binding (e.g., WPF, Blazor) because binding frameworks expect properties.

Common Mistakes and Edge Cases

A common mistake is using a property for a value that changes frequently and has a side-effect-free getter, but then adding expensive logic later. That is fine as long as you understand the impact. Another mistake is forgetting that the set accessor receives the value as the implicit value variable, which can shadow a field with the same name.

public class Example { private int _value; public int Value { get { return _value; } set { _value = value; } // 'value' is the incoming value } }

A subtle edge case occurs with auto-properties and nullable reference types. If you have a non-nullable auto-property, the compiler warns if you do not initialize it. You can use the null-forgiving operator or an initializer to suppress the warning, but the property can still be set to null at runtime if the caller bypasses the compiler checks.

Another edge case is the init accessor. It is only allowed during object initialization, including in object initializers and constructors. After construction, the property is effectively read-only. This is useful for immutable objects but can be confusing if you expect to modify the property later.

Finally, be aware that properties are not virtual by default. If you mark a property as virtual, derived classes can override the getter and setter independently. This is a powerful feature but adds complexity. Overriding a property with a different backing field can lead to inconsistent state if the base class uses the property internally.

Understanding the C# getter setter pattern is essential for writing clean, maintainable code. Properties give you the flexibility to change implementation details without affecting callers, and they are a core part of the language's design. When you need to control access, validate input, or compute values, properties are the right tool.

c# getter setter: Practical Usage and Code Examples | RYUSLOG DEV