Blog Series Tags

C# Abstract Properties, Virtual Properties and Access Modifiers

Properties in C# are first class citizens, this means that they can be declared as abstract, virtual, private and protected. This is not the general use case though, most of the time properties are used to encapsulate fields.

class Customer
{
    public int Number { get; set; }
    public string Name { get; set; }
}
 
Customer customer = new Customer();
customer.Number = 9;
customer.Name = "Mrs Lovett's Pie Shop";

n the example above, auto implemented properties are used to aid encapsulation. The C# compiler automatically generates the fields used to store the properties and also the methods used for access. Externally, the properties look just like public fields but are actually implemented as methods. The setters and getters of a property can have different access modifiers.

public class Customer
{
    public int Number { get; set; }
    public string Name { get; set; }

    public string Description { get; protected set; }
}
 
public class MurderousCustomer :  Customer
{
    public MurderousCustomer()
        : base()
    {
        Description = "A nasty operation.";
    }
}
 
Customer customer = new MurderousCustomer();
Assert.IsTrue(customer.Description == "A nasty operation.");

The Description property defined in the Customer class has a protected set. This means that external clients of the code can’t set values for Description, that does not however stop descendents of the Customer class, like the aforementioned MurderousCustomer doing so. This brings us to another interesting point on Properties. They work well with C#’s inheritance model. A simple change to the Description field can force descendents of the Customer class to implement it.

public abstract class Customer
{
    ...
    public abstract string Description { get; protected set; }
}
 
public class MurderousCustomer :  Customer
{
    public override string Description { get; protected set; }
 
    public MurderousCustomer()
        : base()
    {
        Description = "A nasty operation.";
    }
}

Now, every descendant of the newly abstract Customer class needs to implement the Description property. Notice that we set Description as an abstract property in the Customer class. This is C# inheritance in action, working seamlessly with Properties. Properties can also be defined as virtual, in which case descendents can override the them. Why? Well, since Properties are essentially methods, they can contain logic that validates the data on its way in. Virtual properties are a good way of allowing descendents to customise behaviour.

public abstract class Customer
{
    int rating;
 
    ...
    public int Rating
    {
        get { return rating; }
        set
        {
            if (rating < 5)
                rating = value;                
        }
    }
 
    public Customer()
    {
        rating = 1;
    }
 
    ...
}
 
Customer customer = new MurderousCustomer();
 
for (int i = 0; i < 10; i++)
    customer.Rating++;
 
Assert.IsTrue(customer.Rating == 5);

Consider the above class. Now we have a rating system for customers. There is validation logic in the setter than ensures that customers cannot be rated above 5. Also notice the nifty use of the increment (++) operator against the Rating property. It works just as though it was a public field. What if you wanted to ensure that MurderousCustomers are always rated to 0?

public abstract class Customer
{
    protected int rating;
    ...
    public virtual int Rating
    {
        get { return rating; }
        set
        {
            if (rating < 5)
                rating = value;
        }
    }
 
    public Customer()
    {
        rating = 1;
    }
    ...
}
 
public class MurderousCustomer : Customer
{
    ...
    public override int Rating
    {
        get { return base.Rating; }
 
        set { base.rating = 0; }
    }
 
    public MurderousCustomer()
        : base()
    {
        base.Rating = 0;
        ...
    }
}
 
Customer customer = new MurderousCustomer();
for (int i = 0; i < 10; i++)
    customer.Rating++;
 
Assert.IsTrue(customer.Rating == 0);

Now, we’ve made the rating field in Customer protected, allowing MurderousCustomer to access it directly. We’ve also made the Rating property virtual, which allows it to be overridden by MurderousCustomer. We change the validation logic (in this case, we just set the rating field to 0) so that MurderousCustomer now behaves differently from the abstract Customer.