In this article, we will learn about the difference between Properties and Fields in C#, and how to use them.
Let’s begin!
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 application domain. The static fields are accessible without instantiating the type, similar to global variables.
In essence, a static field acts as a “singleton” within the context of the application, ensuring a single shared value across all instances of the class.
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
. This requires us to initialize the required
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 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:
public string Name { get; set; }
These properties automatically generate a private backing field and provide a default implementation for the get and set accessors.
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. This allows 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;
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 application domain.
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.
Difference Between Properties and Fields
The major differences between properties and fields in C# lie in their accessibility, encapsulation, and the level of control they provide over data access.
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.
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.
When To Use Properties and Fields
One of the main differences between Properties and Fields involves data encapsulation. Another major difference is regarding computations or validations we may wish to perform when data is read or written. Both of these are things we need to consider when choosing between exposing our data as a Field or as a Property.
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.