Modernize Your C# Code - Part 1

Want to modernize your C# codebase? Let's start with properties.

Modernizing C# Code

Table of Contents

Introduction

In recent years, C# has grown from a language with exactly one feature to solve a problem to a language with many potential (language) solutions for a single problem. This is both, good and bad. Good, because it gives us as developers freedom and power (without compromising backwards compatibility) and bad due to the cognitive load that is connected with making decisions.

In this series, we want to explore what options exist and where these options differ. Surely, some may have advantages and disadvantages under certain conditions. We will explore these scenarios and come up with a guide to make our life easier when renovating existing projects.

Background

In the past, I've written many articles particularly targeted at the C# language. I've written introduction series, advanced guides, articles on specific topics like async / await or upcoming features. In this article series, I want to combine all the previous themes in one coherent fashion.

I feel it's important to discuss where new language features shine and where the old - let's call them established - ones are still preferred. I may not always be right (especially, since some of my points will surely be more subjective / a matter of taste). As usual, leaving a comment for discussion would be appreciated!

Let's start off with some historic context.

What are Properties?

The idea of properties was not born in C#. Actually, the idea of a mutator method (getter / setter) for a field is as old as software and got quite popular in object-oriented programming languages.

From Java To C#

In Java-ish C#, one would not include any special syntax for such mutator methods. Instead, one would opt-in for code like the following:

class Sample
{
    private string _name;

    public string GetName()
    {
        return _name;
    }

    public void SetName(string value)
    {
        _name = value;
    }
}

By convention, we would always place Get (prefix of a getter method) or Set (prefix of a setter method) in front of the "usual" identifier. We can also identify a common pattern here with respect to the used signatures.

In general, we may say the following interface can describe such a property consisting of a getter and setter:

interface Property<T>
{
    T Get();

    void Set(T value);
}

Of course, such an interface does not exist and even if it would, it would only be a compound interface consisting of two separate interfaces - one for a getter and one for a setter.

Actually, e.g., having a getter-only makes a lot of sense. This is the encapsulation we are quite often looking for. In the following example, only the class itself could determine the value for the _name field. No "outsider" is allowed to perform any mutation, which makes the used mutator already useful.

class Sample
{
    private string _name;

    public string GetName()
    {
        return _name;
    }
}

Nevertheless, since we can already see that many things are here by convention and very repetitive, the C# language team thought we need some syntactic sugar on top of "classic" mutator methods: properties!

Useful for Avoid for
  • Extension properties (these are just methods after all)
  • Where you need full freedom and want to be very explicit
  • Class properties (for this, we have C# properties)

The Classic Way

Already from the first version of the C# language, we had the (explicit, i.e., classic) way of writing properties. They fix the convention introduced earlier, however, do not give us any other benefit. We still need to write the method bodies (of getter and setter methods) explicitly. Even worse, we have quite some curly brackets to deal with and cannot, e.g., rename the name of the setter value.

class Sample
{
    private string _name;

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

A property in C# looks like a method, however, omits the parentheses (i.e., method parameters). It also forces us to write a block containing either a get method, set method, or both.

Even though this seems a little bit more convenient to write (at least more consistent), it still computes to the same.

Here is the MSIL (compiled intermediate language) as produced by the Java-ish program.

Sample.GetName:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       Sample._name
IL_0007:  stloc.0     
IL_0008:  br.s        IL_000A
IL_000A:  ldloc.0     
IL_000B:  ret         

Sample.SetName:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.1     
IL_0003:  stfld       Sample._name
IL_0008:  ret

And the same result produced when using our new C# properties.

Sample.get_Name:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       Sample._name
IL_0007:  stloc.0     
IL_0008:  br.s        IL_000A
IL_000A:  ldloc.0     
IL_000B:  ret         

Sample.set_Name:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.1     
IL_0003:  stfld       Sample._name
IL_0008:  ret

Notice the difference? They are the same except the name. This is actually crucial. Trying to take this name is no longer possible when we have such a property:

Properties hiddenly reserve a name

Thus with every mutator in a C# property, we do also take away some names that are not directly seen. This seems straight forward at first, but it brings in some complexity (design-time name vs compile-time name) that may not really be wanted or understood.

Useful for Avoid for
  • More complicated logic in a mutator method
  • Computed properties with more logic (no backing field)
  • Being quite explicit while following conventions
  • Simple wrapping of a field

The Modern Way

As we've seen, the classic properties only provide a bit of syntactic sugar to fix the conventions usually used when creating mutator methods. In the end, what we get is 100% the same as what we would have written anyway. Yes, in the metadata properties are also marked as such making it possible to distinguish between a method coming from a property and an explicitly written one, however, the executed code does not see any difference.

With more recent versions of C# (starting with the third iteration), some new concepts have been added to provide even more development convenience. In the following, we refer to all these post-v1 additions in C# as "the modern way".

Auto Properties

Auto properties provide a way to eliminate most of the boilerplate that comes with a "standard" property. A standard property is one that consists of a field that has a getter and a setter. Important here is that both methods are needed, even though the modifier (i.e., public, protected, internal, and private) may be different.

Consider the following straight forward example replacing our previous implementation:

class Sample
{
    public string Name
    {
        get;
        set;
    }
}

Yes, for performance reasons, we may dislike this. The reason is that the field is actually "hidden" (i.e., inserted from the compiler with no access from our side). Hence, the only access to the field is via the property.

This can also be seen in the generated MSIL:

Sample.get_Name:
IL_0000:  ldarg.0     
IL_0001:  ldfld       Sample.<Name>k__BackingField
IL_0006:  ret         

Sample.set_Name:
IL_0000:  ldarg.0     
IL_0001:  ldarg.1     
IL_0002:  stfld       Sample.<Name>k__BackingField
IL_0007:  ret

Nevertheless, OOP purists will tell us that field access should anyway not be done directly and always go via mutator methods. Hence from this view, this is actually a good practice. .NET performance experts will also tell us that such automatic properties will have no penalties as the JIT will inline the method resulting in a direct modification anyway.

So is all good with this approach? Not really. There is no way to mix this approach with custom logic in the setter (e.g., only setting the value if it's "valid"). Either we have both (getter and setter) methods in a standard implementation or we need to be explicit about both.

Regarding modifiers on this one:

class Sample
{
    protected string Name
    {
        get;
        private set;
    }
}

The outer modifier (in this case, it's protected) will be applied to both mutator methods. We cannot be less restrictive here, e.g., it would not be possible to give the getter a public modifier as it's less restrictive than the already specified protected modifier. Nevertheless, both can be adjusted at will to be more restrictive, however, only a single mutator method can be re-adjusted with respect to the outer (property) modifier.

Remark: While the modifier case for public, protected, and private are obvious, the internal modifier is kind of special. It is more restrictive than public and less restrictive than private, however, both, more and less restrictive than protected. The reason is simple: While protected can be accessed outside of the current assembly (i.e., less restrictive), it also prevents access from non-inherited classes within the current assembly (i.e., more restrictive).

While in theory, we could apply a modifier to both mutator methods, the C# language forbids this with good reason. We should specify a clear and plausible accessibility pattern, which excludes mixed accessors.

Useful for Avoid for
  • Simple fields as properties
  • Simple and concise field + property creation
  • Non-simple fields (e.g., readonly)
  • Custom logic / behavior desired
  • Mixing generated getter / setter with non-generated getter / setter

Assigned Properties

Quite often, the only wish we have is for a property that reflects a certain field. We do not want any setter mutator for it. Unfortunately, with the previous approach, we do not get the field and cannot remove or omit the setter.

Luckily, already the first proposal solves this. We can freely omit one of the two mutator methods.

Let's see this in action:

class Sample
{
    private string _name = "Foo";

    public string Name
    {
        get { return _name; }
    }
}

Well, what use does this have? Besides being now restricted to setting the value only via the field (making it obvious that there is no hidden magic involved when setting the value), we do not see any clear advantage right now.

For completeness, the constructed MSIL looks like:

Sample.get_Name:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       Sample._name
IL_0007:  stloc.0     
IL_0008:  br.s        IL_000A
IL_000A:  ldloc.0     
IL_000B:  ret         

Sample..ctor:
IL_0000:  ldarg.0     
IL_0001:  ldstr       "Foo"
IL_0006:  stfld       Sample._name
IL_000B:  ldarg.0     
IL_000C:  call        System.Object..ctor
IL_0011:  nop         
IL_0012:  ret

The major advantage in this case is the ability to set the field also readonly (or giving it any other arbitrary attribute, modifier, or initialization logic).

class Sample
{
    // will always have the value "Foo"
    private readonly string _name = "Foo";

    public string Name
    {
        get { return _name; }
    }
}

In comparison to the public string Name { get; private set; } way we can for sure exclude the possibility of setting the value after initialization (this guarantee is not only communicated to one self in the future, but also to any other developer that may cross this field).

Useful for Avoid for
  • Exposing a single field with complicated logic
  • Being very explicit and having full control what's happening
  • Almost everything!

Now all we need is a way to combine our desire for writing readonly fields / properties with auto properties.

Readonly Properties

In C# 6, the language design team picked up this idea and delivered a solution to the problem. C# now allows getter only properties.

In practice, these properties can be assigned like a readonly field, e.g., in the constructor or directly when declaring them.

Let's see this in action:

class Sample
{
    public string Name { get; } = "Foo";
}

The generated MSIL is similar to the auto properties (who would have guessed), but does not have any setter method. Instead, what we see is an assignment to the underlying field that has been generated.

Sample.get_Name:
IL_0000:  ldarg.0     
IL_0001:  ldfld       Sample.<Name>k__BackingField
IL_0006:  ret         

Sample..ctor:
IL_0000:  ldarg.0     
IL_0001:  ldstr       "Foo"
IL_0006:  stfld       Sample.<Name>k__BackingField
IL_000B:  ldarg.0     
IL_000C:  call        System.Object..ctor
IL_0011:  nop         
IL_0012:  ret

If we compare this code with the one for our explicit (readonly) field, we see that both are the same in the initialization. There is no functional difference here, however, for the getter, the code is much smaller and more direct. The reason is that since the C# compiler is responsible for generating the code (i.e., backing field with access), it will just skip some validation / safety calls. For performance reasons, we may say that this is an advantage for the current version, but keep in mind that we'll only see unoptimized non-jitted code here. The JIT may actually remove all of the former boilerplate and inline the remaining field load.

Useful for Avoid for
  • Exposing a readonly variable as property
  • Need to access or modify a field directly
  • Situations where flexibility in the implementation is necessary

With this in mind can we get even more simple while improving flexibility?

Property Expression

A great feature in C# 3 has been the introduction of LINQ. With it, a full bouquet of new language features has been introduced. One of the great features has been the lambda syntax for writing anonymous functions (in C#, we may also call these function references delegates, while in, e.g., C++ they are called functors). This lambda syntax has been a central element in C# 7 upwards for making C# more functional / friendly for patterns found in functional programming (FP).

One of these enhancements has been to C# properties, which can now be resolved as an expression using the "fat arrow", i.e., lambda syntax.

This can look as simple as the following code shows:

class Sample
{
    private readonly string _name = "Foo";

    public string Name => _name;
}

We could also (ab)use this syntax to come up with something even more trivial, e.g., public string Name => "Foo", which works even better in this special case, but is in general not the same or advised.

Nevertheless, in some scenarios where a property is only a shallow wrapper around some other functionality (e.g., lazy loading) such syntax may be ideal.

Sample.get_Name:
IL_0000:  ldarg.0     
IL_0001:  ldfld       Sample._name
IL_0006:  ret         

Sample..ctor:
IL_0000:  ldarg.0     
IL_0001:  ldstr       "Foo"
IL_0006:  stfld       Sample._name
IL_000B:  ldarg.0     
IL_000C:  call        System.Object..ctor
IL_0011:  nop         
IL_0012:  ret

Notice that the MSIL looks as straight forward as in the readonly properties case? The additional validation we talked about was the safety guarantee given from using a block statement. Now we only use an expression and omit the block. This was not possible beforehand so the MSIL had to reflect the block, now it can be simpler.

Useful for Avoid for
  • A (standard, i.e., getter only) computed property
  • Being verbose about exposing a field
  • Wrapping a readonly variable
  • Exposing an arbitrary variable

What if we want to use the expression syntax shown above, but the property also needs a setter method?

Get and Set Expressions

Luckily, the C# language design team also thought on this case. We can actually use a combination of a standard property (as found in C# 1.0) with the expression syntax.

In practice, this looks as follows:

class Sample
{
    private string _name = "Foo";

    public string Name
    {
        get => _name;
        set => _name = value;
    }
}

Modifiers are also not a problem and are added naturally as in the original C# specification. The MSIL does not give us any surprises.

Sample.get_Name:
IL_0000:  ldarg.0     
IL_0001:  ldfld       Sample._name
IL_0006:  ret         

Sample.set_Name:
IL_0000:  ldarg.0     
IL_0001:  ldarg.1     
IL_0002:  stfld       Sample._name
IL_0007:  ret         

Sample..ctor:
IL_0000:  ldarg.0     
IL_0001:  ldstr       "Foo"
IL_0006:  stfld       Sample._name
IL_000B:  ldarg.0     
IL_000C:  call        System.Object..ctor
IL_0011:  nop         
IL_0012:  ret

Indeed, the getter is simpler as in the original version and even the setter profited from not being in a block statement (4 instead of 5 instructions).

Useful for Avoid for
  • A computed property with a setter method
  • Being ultra flexible, yet lightweight and concise
  • A property with no special logic (i.e., directly exposing a field)
  • A property without a getter or setter
  • Exposing a readonly field

Outlook

In the next part of this series, we will take on methods as a follow up to properties. While boring at first, we will specifically include the evolution of delegates in there, as well as newer constructs such as local functions.

As far as the future of properties is concerned, the next level may be to have a property counter-part for extension methods (i.e., read extension properties). Right now, this is solved by falling back to the Java-ish syntax / convention again.

Conclusion

The evolution of C# has not stopped at properties. Once added to the language as a little bit of convention-safer, they evolved to functional constructs exposing fields and making things like lazy loading enjoyable to work with.

We can only hope that the journey is not over here. Many fruitful features can be added and many have been desired in the past (e.g., to mitigate the boilerplate needed for WPF binding).

Points of Interest

I always showed the non-optimized MSIL code. Once MSIL code gets optimized (or is even running), it may look a little bit different. Here, actually observed differences between the different methods may actually vanish. Nevertheless, as we focused on developer flexibility and efficiency in this article (instead of application performance) all recommendations still hold.

If you spot something interesting in another mode (e.g., release mode, x86, ...) then write a comment. Any additional insight is always appreciated!

History

  • v1.0.0 | Initial release | 03.03.2019
  • v1.1.0 | Added Table of Contents | 05.03.2019
  • v1.2.0 | Added Auto Readonly Properties | 07.05.2019
Created .

References

Sharing is caring!