Click here to Skip to main content
13,900,622 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

9.7K views
24 bookmarked
Posted 3 Mar 2019
Licenced CPOL

Modernize Your C# Code - Part I: Properties

, 5 Mar 2019
Rate this:
Please Sign up or sign in to vote.
Want to modernize your C# codebase? Let's start with properties.

Modernizing C# Code

Introduction

In the 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 targetted 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 its 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 detrmine 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 parantheses (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 nethod
  • 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 its "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 its 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 its 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 hacing 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 generatd 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 be 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Florian Rappl
Chief Technology Officer
Germany Germany
Florian lives in Munich, Germany. He started his programming career with Perl. After programming C/C++ for some years he discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics.

During his studies he worked as an IT consultant for various companies. After graduating with a PhD in theoretical particle Physics he is working as a senior technical consultant in the field of home automation and IoT.

Florian has been giving lectures in C#, HTML5 with CSS3 and JavaScript, software design, and other topics. He is regularly giving talks at user groups, conferences, and companies. He is actively contributing to open-source projects. Florian is the maintainer of AngleSharp, a completely managed browser engine.

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Shaun Stewart6-Mar-19 2:12
memberShaun Stewart6-Mar-19 2:12 
GeneralRe: My vote of 5 Pin
Florian Rappl6-Mar-19 7:14
professionalFlorian Rappl6-Mar-19 7:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190306.1 | Last Updated 5 Mar 2019
Article Copyright 2019 by Florian Rappl
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid