Updated on

A field stores data. A property is a pair of accessor methods we call with field syntax, and that indirection is the entire difference. It is where validation, computation, and change notification live.

The rule that follows from it is short: expose properties, keep fields private. A public field cannot be given validation later without breaking every caller that was compiled against it, and it is invisible to most serializers, model binders, and data-binding engines.

To download the source code for this article, you can visit our GitHub repository.

What Is a Field?

We call a variable that we directly declare in a class or struct a “field” in C#. Fields can be of any type and can be public, private, protected, internal, protected internal, or private protected. These access modifiers used with our fields define their level of access:

private int _age;

Here, _age is a field of type int and is marked private, meaning it can only be accessed from within the class.

A common use case for fields is as a backing store or backing field. This is when we declare a field as private and use it to store data accessed by a public property.

Let’s declare an Age property and use the _age field as its backing field:

public int Age
{
    get { return _age; }
    set { _age = value; }
}

When we instantiate an object, the compiler initializes the fields before calling the object constructor. However, we can overwrite any value that the field has at the time of declaration.

Let’s see this in action by creating a Person class:

public class Person
{
    private string _name = "John Doe";

    public Person()
    {
        Console.WriteLine(_name);

        _name = "Jane Doe";
    }

    public void UpdateName(string name)
    {
        Console.WriteLine(_name);

        _name = name;

        Console.WriteLine(_name);
    }
}

Here, we set a value to the _name field at the time of declaration. Then, we update the value in the constructor. Finally, we provide an additional means to update the value using the UpdateName() method. 

Now, if we instantiate this class object and call the UpdateName() method:

var person = new Person();
person.UpdateName("Sam Doe");

We see the field value being updated thrice in the console output in the order we updated the values:

John Doe
Jane Doe
Sam Doe

Types of Fields

We can declare a field readonly. This means that we can only assign a value to the field at the time of declaration or within the constructor. Making the _name field read-only would cause a compiler error in the UpdateName() method.

Another keyword that we can use with a field is static.

Once we declare a field as static, the field gets associated with the type itself rather than with instances of the type. This means that we have only one instance of the static field across all instances of the class within the same process. The static fields are accessible without instantiating the type, similar to global variables.

Let’s add a static Age field:

public class Person
{
    public static int Age;
    private string _name = "John Doe";
}

We can now access the Age field without instantiating a Person object:

Person.Age = 19;

A field can be both static and readonly at once. The static readonly fields are similar to constants. However, in contrast with constants, their values are resolved at runtime rather than at compile time.

We can declare a field as required. As with all required members, this requires us to initialize the field by an object initializer when creating an object. Let’s declare a required HasSuperPowers field in the Person class:

public required bool HasSuperPowers;

This triggers a compiler error “Error CS9035 Required member ‘Person.HasSuperPowers’ must be set in the object initializer or attribute constructor”. To fix this error, we need to add an object initializer:

var person = new Person { HasSuperPowers = true };

Since C# 12, we have primary constructors that can act as a replacement for the fields. The parameters of a primary constructor can initialize properties or fields used as variables in methods or local functions. In addition, we can pass them to a base constructor.

Now that we’ve gone over fields in C#, let’s take a look at properties.

What Is a Property?

In C#, properties are a way to encapsulate private fields, providing controlled access to them through getter and setter methods. We use the get accessor to retrieve the value of the property and the set accessor to assign the value to the property. The set  accessor has an implicit parameter called value, with a type matching that of the property. We cover the full range of what accessors can do in a full walkthrough of properties in C#.

We have properties with a backing field like the Age property we saw earlier:

public int Age
{
    get { return _age; }
    set { _age = value; }
}

We can also have auto-implemented properties:

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

public string Name { get; set; }

These properties automatically generate a private backing field and provide a default implementation for the get and set accessors. There is more than one way of assigning an initial value to auto-properties.

Types of Properties

Both of these properties are “read-write” properties. We can also have “read-only” properties with only get accessors, or “write-only” properties with only set accessors. Let’s create a Configuration class:

public class Configuration
{
    private string _secretKey = string.Empty;

    public string SecretKey
    {
        set
        {
            _secretKey = $"**{value}**";
        }
    }

    public string MaskedSecretKey
    {
        get { return _secretKey; }
    }
}

Here, we mask the actual value for security reasons using the “write-only” SecretKey property. Then, the MaskedSecretKey property, which is a “read-only” property, can be used to retrieve the masked value.

Init Only Properties

Since C# 9.0, we also have an init accessor available. These init-only setters allow us to set the initial value of a property during object creation, but then prevent further modifications to that property:

public class Rectangle
{
    public double Width { get; init; }
    public double Height { get; init; }
}

Now, we can create an instance of the Rectangle class and set its properties:

var newRectangle = new Rectangle { Width = 10, Height = 5 };

However, we can’t modify these properties later:

newRectangle.Width = 15.0;

This gives us a compiler error.

Static Properties

Similar to the fields, we can add access modifiers to properties to control their accessibility. We can also declare properties as static using the static keyword. For example, we can add a static ScalingFactor property to the Rectangle class:

public static double ScalingFactor { get; set; } = 1.0;

Because static properties are bound to the class itself, rather than a specific instance, we can directly access them without instantiating a class:

Rectangle.ScalingFactor = 2.0;

The Rectangle class declares two constructors alongside those properties, a parameterless one and one that takes the two dimensions:

public Rectangle() { }

public Rectangle(double width, double height)
{
    Width = width;
    Height = height;
}

Now, let’s create a CreateScaledRectangle() method to apply scaling:

public Rectangle CreateScaledRectangle()
{
    return new Rectangle(Width * ScalingFactor, Height * ScalingFactor);
}

Finally, let’s create another Rectangle and scale it:

var newRectangle = new Rectangle { Width = 10.5, Height = 5.5 };
var newRectangleScaled = newRectangle.CreateScaledRectangle();

Console.WriteLine("Dimensions of the new rectangle after scaling: "
    + $"{newRectangleScaled.Width} X {newRectangleScaled.Height}");

We observe that the scaling factor we set previously (2.0) is applied to the newRectangle object, even though we did not set a scaling factor during its construction:

Dimensions of the new rectangle after scaling: 21 X 11

This is because there is only one instance of the static property across all instances of the class within the same process.

Virtual and Abstract Properties

A property can be declared as virtual by marking its accessor using the virtual keyword. Virtual properties allow inheriting classes to override them using the override keyword. With virtual properties, we can provide a default behavior for the property in our class but still allow inheritors to define specific behavior.

We can also declare a property as abstract, but this can only be done within an abstract class. Abstract properties do not provide an implementation in the base class but rather require deriving classes to provide an implementation.

What Is the field Keyword in C#?

field is a contextual keyword that names the compiler-generated backing field of a property from inside that property’s own accessors.

Before it, adding a single line of validation to an auto-property meant giving the auto-property up. We declared a private field, wrote both accessors by hand, and kept two names in sync forever.

With field, the property keeps its short form and we write only the accessor that needs the logic. The compiler still generates the storage; we simply get a name for it.

That shifts the boundary this whole article is about. The choice used to be a field plus a property against a bare auto-property. Now a property can hold private state without any field appearing in our source at all.

One trap is worth knowing. Inside a property accessor, field binds to the backing field, so a local or parameter with that name is shadowed. Use @field or this.field to reach the shadowed member.

Microsoft’s properties guide pins the version: “In C# 14, you can add validation or other logic in the accessor for a property using the field keyword.”

Let’s write the same width validation the sample writes by hand, using field instead:

public double Width
{
    get;
    set => field = value >= 0
        ? value
        : throw new ArgumentException("A Rectangle can't have negative width", nameof(value));
}

There is no _width declaration anywhere, and no second name to keep in sync.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

What Is the Difference Between Properties and Fields in C#?

Fields store data. Properties control access to it.

A field is a variable declared directly in a class or struct. Reading or writing it is a direct memory access, and there is nowhere to put validation, a computed value, or a notification.

Microsoft’s C# properties guide states the mechanism: properties “appear as public data members, but they’re implemented as special methods called accessors. That indirection is the difference, and everything else follows from it.

Because a property is methods, it can validate on assignment, compute on read, be virtual or abstract, appear in an interface, and expose reading and writing at different accessibility levels. A field can do none of those.

Because a field is storage, it is the one that can be readonly, and the one we can pass by ref or out.

The practical consequence is the rule: public members are properties, private state is fields.

Accessibility and Direct Access

Fields provide direct access to data and are often declared with keywords like private, public, or protected to specify their visibility. They lack the protective mechanisms of encapsulation, making them directly accessible from outside the class.

Properties, on the other hand, encapsulate data and control access through accessors. This allows for more controlled visibility and modification of data.

Encapsulation

Fields lack encapsulation, meaning they can expose data without any inherent protection or validation, like a field with public access modifier which allows for direct access from outside the class.

Properties encapsulate data, enabling us to control how we access and modify the data. For example, by using validation logic we can ensure that we only set valid and desired data. Encapsulation and abstraction are easy to confuse, so we also cover the difference between abstraction and encapsulation.

Usage of Get and Set Accessors

We access the fields directly without the need for explicit accessors. Whereas, properties utilize get and set accessors, allowing for additional logic during access or modification. For instance, we can introduce a validation logic for a rectangle’s width:

public double Width
{
    get => _width;
    init
    {
        if (value < 0)
            throw new ArgumentException("A Rectangle can't have negative width",
                nameof(value));

        _width = value;
    }
}

This is only possible using properties.

Should We Use a Property or a Field in C#?

Expose properties. Keep fields private.

Every public member of a type should be a property, even one that does nothing but return a value. The reason is not style. Turning a public field into a property later changes the compiled shape of the type, so callers compiled against the old version have to be rebuilt.

Fields stay for the storage a type keeps to itself: a backing field, a cached value, a readonly dependency assigned in the constructor.

There is a second reason that costs people more time. Serializers, model binders, and validation attributes read properties and skip fields by default, so a public field quietly disappears from the feature that was meant to use it.

The overhead argument does not hold. A property that only forwards to its backing field is inlined by the JIT, so we pay nothing for the option to add logic later.

CriterionFieldProperty
What it isA variable, storage inside the objectA get/set accessor pair the compiler lets us call like a variable
Validate on writeNot possibleset or init can validate and throw
Compute on readNot possibleget can compute
Can be readonlyYesNo; use init or a get-only property
Can be virtual, abstract, overrideNoYes
Allowed in an interfaceNoYes
Different accessibility per directionNoYes, for example public int X { get; private set; }
Pass by ref or outYesNo
Seen by serializers and model bindingUsually skipped by defaultYes, by default
Changing one into the other laterBinary-breaking for callersNot applicable
Use it forPrivate state, backing storage, readonly dependenciesEvery public member

The mechanism behind every row of that table is one step of indirection:

A field is read and written directly, while a property routes the same call through get and set accessors

Difference Between Properties and Fields Regarding Encapsulation

Because Fields provide direct access to data they are suitable when simple data access is a priority. Fields are useful for a simple domain model and other scenarios where we don’t need to perform extra validation on the values within our application. Because of the direct access to the data, the caller of our class or struct can directly modify the internal state of the object. 

On the other hand, Properties allow us to control access to the underlying data structure while also providing a means for additional validation or computation. This encapsulation allows us to ensure that our object maintains a valid internal state.

Difference Between Properties and Fields Regarding Computed Values

While Fields provide simple and direct access to the underlying data, Properties have the added advantage of allowing us to perform computations on the data before either setting or returning the value.

For example, we may have a class modeling the temperature. Internally we store the value in Kelvin. We can then provide Property accessors which will enable callers to both get and set the value in either Fahrenheit or Celsius, something that would not be possible with direct Field access.

Conclusion

In this article, we learned about fields and properties in C#. We looked at how to use them and when to use one over the other. The choice between them depends on the desired level of control and encapsulation for a particular data member within a class.

Tested with .NET 10.0.10.