Note: A video version of this article is also available on YouTube: JeremyBytes - C# Properties.
Property with a Backing Field
We'll create a simple "Person" class to explore properties. Here's a typical property that has a backing field:
First, we have a private backing field ("firstName"). Then we have our public property ("FirstName") that has both a getter and a setter to access the backing field. The getter returns the value of the backing field, and the setter sets the value of the backing field.
Note: You can easily create a property with a backing field using the "propfull" snippet in Visual Studio. To do this, go to a blank line and type "propfull". IntelliSense will show this as "Code snippet for property and backing field". When you press Tab, it stubs out the code for you. Then you get 3 highlighted items. The first ("int" by default) is the type. Just update this with the type you want ("string" in our case), and press Tab. This will take you to the name of the backing field. Update the name and press Tab again to go to the name of the property. When you're done, just hit Enter. Snippets are awesome for this kind of stuff.
This syntax looks a bit strange at first, but it's simply a shorthand way encapsulating a field. This will become a bit more clear when we compare it to another language.
Get / Set Methods
Java has a different paradigm for this type of encapsulation. The data is still hidden in a private field, but a pair of methods (get / set) are used to access this data. Here's a phone number "property" that uses this type of syntax:
(Note: this is still C# code, but it's written in the Java property style -- I've also used the "curly braces on the same line" format, but we already know that this doesn't really matter). This code makes things a bit more clear. We have encapsulated our data with a pair of accessor methods.
By encapsulation, I mean that we have hidden our data ("phoneNumber") and provided specific methods ("get_PhoneNumber" and "set_PhoneNumber") that we can use to interact with the data from the outside world . We could easily create a read-only property by simply omitting the "set" method. Our data is safely hidden behind a public interface, and we can be confident that it cannot be modified without our class knowing about it.
.NET Properties are Methods
It turns out that properties in .NET also use get and set methods to control access to the data. The difference is that these methods are hidden from our view. Let's look at the IL that is generated by the code we've seen so far.
We'll use ildasm.exe to look inside our assembly. If you want to see how to add IL DASM to your Visual Studio tools, take a look at this article: Book Review: Professional .NET Framework 2.0 (yes, the instructions are part of a book review I did).
Here's the overview of our class in IL DASM:
There are a couple of interesting things here. First, notice our backing fields (with the teal diamonds). Both "firstName" and "phoneNumber" are private string fields. This is exactly what we would expect.
In our methods (the purple squares), we see the "get_PhoneNumber" and "set_PhoneNumber" methods that we created ourselves. But we also see "get_FirstName" and "set_FirstName". These methods were generated by the compiler from our property.
The last item (the red triangle) is our "FirstName" property. Here's what it does:
This says that when someone tries to "get" the property, the method "get_FirstName" should be called. When someone tries to "set" the property, then then method "set_FirstName" should be called. This is how our property gets wired up to those compiler-generated methods.
If we compare the getters for our two properties, we see that the look almost exactly the same:
When we look at the method body, we see "ldarg.0" (load argument) -- this is at the beginning of every instance method. It pushes "this" (which is argument 0) onto the stack so that it can be used in the method. Next, we have "ldfld" (load field) -- this pushes the value of the specified field onto the stack. We can see that the each method pushes "firstName" or "phoneNumber" as appropriate.
Finally, we have the "ret" (return) -- this returns the value on the top of the stack, which happens to be the field that we just pushed.
Note: This sample is built as "Release". If you build as "Debug", then you may see some "nop" (no operation) instructions. There are placeholders so that you can set breakpoints on the curly braces in the source code.
If we look at the method declaration (at the top), we see that these two methods are *almost* identical. The difference is that "get_FirstName" has the "specialname" attribute specified. This means that "get_FirstName" is a special method that cannot be called by user code. If you try to put "person.get_FirstName()" in your code, you'll just get a compiler error. We aren't allowed to access this method directly.
Other than that, we can see that there's no real difference between our manual getter (for the "phoneNumber" field) and the compiler-generated getter (for the "FirstName" property).
Automatic Properties
In C# 3.0 (.NET 3.5), we got some new syntax for creating properties: automatic properties. Here's a sample:
The automatic property syntax is simply a shortcut. If we don't need to do anything special in the getter or setter (more on this below), then this shorthand saves us from having to type out the standard implementation of the getter and setter (it also saves us from having to create a separate backing field).
Note: Automatic properties can be created with the "prop" snippet. This only has 2 editable items: the type and the property name. This snippet doesn't save nearly as much typing as "propfull", but it can still be useful.
So, let's compare our full property ("FirstName") to the automatic property ("LastName") to see what the compiler builds for us. First, an overview:
We now have a new field: "<LastName>k__BackingField". So, even though we don't create a backing field in our code, the compiler generates one for us. It uses the strange name so that there won't be any potential naming conflicts with non-generated code.
Here's the IL for the "firstName" field and the "<LastName>k__BackingField":
There's no surprises for the "firstName". It just shows it as a private string field. Notice that the first line of the LastName backing field looks similar: it just shows a private string field.
The next line is an attribute noting that this field was generated by the compiler (there's actually a bit more code that has been truncated from this screenshot).
Finally, let's compare the getters for these two properties:
Here we see that the getters are exactly the same (except for the CompilerGeneratedAttribute). This shows us that if we use automatic properties, we get (almost) exactly the same code as if we use a property with a backing field.
One thing to note about automatic property syntax is that we must have both the "get;" and the "set;". If you want to make a read-only property, this is easy to get around: just make the setter private. For example:
This has the effect of making the property read-only to the outside world. It can only be set by the class internally.
Why Don't We Always Use Automatic Properties?
So, since we see that the property with the backing field and the automatic property generate the same IL, why would we ever want to use a property with a manual backing field?
There are a couple of answers to this. First, sometimes we want to do some more work in the getter (other than just retrieving the value of the backing field). For example, we may want the getter to supply a default value for a property if the backing field is null. We may also want to use Property Injection (see: Dependency Injection: A Practical Introduction).
More frequently, we want to do more work in the setter. We can put validation code in the setter that would reject an invalid value. Or we can put security code in the setter that would only update the backing field if you have proper authorization. Or we may want to fire an event to notify other objects that the value has changed.
This last scenario is very common in the XAML and WinForms databinding world. "INotifyPropertyChanged" is an interface that specifies an event that can be fired when data is changed programmatically. This notifies the UI that something is different and it needs to update the values that are displayed in the controls. Here's an example of this type of setter:
Here we see that the setter has a bit more code in it. First, we check to see if the incoming value already matches what's in the backing field. If the data is unchanged, we "return" -- short-circuiting the rest of the setter. Otherwise, we set the backing field and then raise the notification event.
Optimization Note: the "if" statement is not required; it just saves the firing of the notification event. But conditionals also have a little bit of overhead, so you may want to re-evaluate whether the conditional is advisable based on your particular application.
So, we see that there are a few reasons why we may want to supply our own backing field and get/set implementation.
Wrap Up
The .NET property syntax is a bit odd when you first see it, especially if you are coming from a language that doesn't have this type of property implementation. As we've seen, properties are just sets of methods that help us encapsulate our data.
Whether we create our own backing fields or use automatic properties depends on how we're using them. For example, a DTO (Data Transfer Object) is just used to move data around. For these, we can use automatic properties. In contrast, if we have a View Model with properties that need to be databound, we'll probably want to have "full" properties so that we can fire notification events when the values are changed in the setters.
Properties are everywhere, and they are incredibly useful. Hopefully, this has given you a better insight into what's going on "under the covers."
Happy Coding!
No comments:
Post a Comment