One of the first things you will learn while exploring C# and OOP (Object-Oriented Programming) is properties. Properties in C# are powerful, although they might seem pretty simple. They are very important when it comes to developing software in C#. Properties help your classes secure and they make sure data is handled properly.
In this article, we’ll dive into the basics of properties in C#. We will learn how to use getters and setters to control access to your class’s data, explore different types of properties in C#, and discover some best practices.
Table Of Contents
What Are Properties in C#?
In C#, properties are located in a class and define how values are read from or assigned to fields within that class. We could access fields, like variables inside a class, directly, but properties ensure our data is validated and secure. Properties in C# are also a way to send data from one class to another. Yes, we could use the parameters of a method, but properties are better for validation and readability.
Properties also make our code cleaner and easier to read. We can give them good, understandable names with dedicated data types.
Here is an example of a class with properties and how they are used.
Product Product = new() { DiscountPercentage = 10, Price = 9, Name = "Luxury taco's", Stock = 9 }; public class Product { public string Name {get; set; } public decimal Price { set { _price = value; } } public bool SoldOut { get { return Stock <= 0; } } public int Stock { get; set; } public int DiscountPercentage { get; set; } public string Description; private decimal _price; public decimal CalculatePrice() { return _price* (DiscountPercentage / 100); } }
The get and set explained
Properties in C# use the get and set keywords. You can compare them to methods but without the whole body. You can add simple logic to it. The { get; set; } tells us that the property has a getter and setter, meaning we can read and write to it. You already saw them in the previous example:
public int Stock { get; set; }
When a property has a get and a set it tells us we can read and write the value of the property. We can also only use the get, like the SoldOut property. This is more of a method, but simpler. A get always needs a return keyword!
public bool SoldOut { get { return Stock < 0; } }
A property with only a get is read-only and it can’t be set, not from inside the class nor from outside the class.
Properties in C# can also only have a set, but that would require private properties. Unlike public properties, private properties can’t be read from outside the class. Remember that a get will return the value of the property. If there isn’t a get it can’t return the value.
public decimal Price { set { _price = value; } } ... private decimal _price;
The _price is not really a property. It is what we call a backing field. It’s a field that backs the public property.
Not a property
The Description doesn’t have a get or a set. Is this a property? No, this is what we call a public field. We can access this field directly from inside the class or outside the class.
So, why use a property or field? You could use both for something, but the difference is that a property has a mechanism to read, write, or compute values of private fields. Think about validation, logic, or other conditions. Also, properties are more flexible because they can create logic and control how the values of those properties are returned or set.
A field is, just like a property, declared inside a class or a struct, but it’s more of a variable. They don’t do any form of validation, except the validation of the data type, but that is something you will notice during development. You approach a field directly, without any special methods or syntax.
We generally use a field for internal usage.
Both a property and a field can be private or public. The difference is that a property’s setter and getter can be controlled for access too.
Use fields for internal data storage that do not require additional logic or validation. And it’s best to use properties when you want to control access to data, enforce validation, or perform logic (like lazy loading, calculations, etc.) when getting or setting the value.
Adding some logic
The property SoldOut has logic in the getter. What it does is return true when the stock is lower or equal to 0 and false when it’s not. Pretty simple, right? We will receive true or false when we call or use this property. It is not possible to set the value ourselves! Important part!
But we can do much more. We can also transform the data and then return it. Take, for example, the method CalculatePrice(). It takes the private variable _price and calculates the new price with the discount. We can also out this logic in a getter on the Price property.
public decimal Price { set { _price = value; } get { if(_price > 0 && DiscountPercentage > 0) { return _price * (DiscountPercentage / 100); } else { return _price; } } }
In this example, I have added the get. When the value of Price is used, the getter will check if the price is higher than 0 and if the DiscountPercentage is set. If the price is 0 or lower or there is no discount, there is no need to calculate the discount price.
We can also set logic on the setter. A default value or check if the user is allowed to use it. Or something else.
set { if (value < 0) { throw new ArgumentException("The price can't be lower than zero!"); } _price = value; }
Enhance Properties in C# with Attributes
Properties can be decorated with attributes. Attributes give properties extra metadata and they also work on classes and methods. These attributes provide useful information that can be used at runtime or during compilation. When combined with properties, attributes enable us to define validation, customization, and formatting rules directly within our code.
Here is an example of some properties decorated with attributes:
public class Product { [Required] public string Name { get; set; } public decimal Price { get; set; } public bool SoldOut { get; set; } [Range(0, 100)] public int Stock { get; set; } [Obsolete("Use the method CalculatePrice method")] public int DiscountPercentage { get; set; } public decimal CalculatePrice(int discountPercentage = 0) { if (discountPercentage > 0) { return Price * (discountPercentage / 100); } else { return Price; } } }
The attributes explained
Required is a well-known attribute and it will check if the value of the property is set. This only works when a user form is submitted or when an API is receiving an HTTP request. If you want to set a property required and make it give an error during development, use the keyword required.
The Range(min, max) is also a pretty well-known attribute. It will tell you that the property only accepts a value between the min and the max. In the case of Stock, you can only set a value between 0 and 100. This also only works when a user form is submitted or when an API is receiving an HTTP request.
The Obsolete() attribute is one that will tell people, who use your library, that the property is no longer being used. Giving it a message could clarify what the developer should do next.
Another pretty popular attribute is the JsonPropertyName-attribute. If you create an object that is being used to convert JSON to a C# object, but the property name of the JSON field isn’t what you want, you can decorate the C# property, with the correct name, with this attribute. In short, you tell C# “This JSON property goes here”.
These are a few attributes you can use, but there are many more. Each with there own purpose and parameters. You can also create your own attribute, if needed.
Best Practices for Using Properties in C#
Here are a few tips, best practices if you will, for the usage of properties in C#:
Simple and consistentcy
Keep them simple! Although this goes for everything you do when developing software, properties can be a bit misused. It’s, for example, not a good idea to put a lot of logic inside the getter or setter. This could lead to unexpected performance issues.
Use clear and consistent names. This is a no-brainer, since this goes for everything you name; classes, variables, methods, enums, etc. It’s generally a good idea to follow the C# naming conventions for this.
Read-only with private and validations
If you want to make your setter read-only, you can also use a private setter. This is considered a better way to make it private. To do this you put the keyword ‘private’ in front of the set;, like this:
public decimal Price { get; private set; }
You can add validations to the setter if you feel it is appropriate. But make sure you don’t overdo it. Like said before in this chapter: Keep them simple. If a setter needs one or two validations you can give it. However, if it needs more or a more heavy validation, make a method and use that.
Immutable properties
Consider immutable properties when possible. Immutable properties make your code safer and easier to debug. The object’s state cannot be changed after it is created, so when data doesn’t change after initialization, make properties read-only and set the value through the constructor. Like this:
public class Product { public string Name { get; set; } public decimal Price { get;} public Product(string name, decimal price, int discountPercentage) { Name = name; if (discountPercentage > 0) { Price = price * (discountPercentage / 100); } else { Price = price; } } }
I bet there are more best practices, but these are the most important ones… Also a bit personal 😉
Conclusion
Properties in C# are a powerful tool that enables you to control access, validate data, and keep your code clean and organized. With automatic properties, getters, setters, and validation, properties offer many ways to handle data safely and securely in your classes.
Now that you’ve got a good understanding of properties, try creating a few on your own! Practice creating properties with validation, calculated values, or read-only access. With time, you’ll get a feel for when and how to use properties effectively in C#. Happy coding!