Tuesday, September 24, 2019

C# 8 Interfaces: Properties and Default Implementation

In taking a closer look at C# interfaces, we'll start by exploring default implementation and how it relates to properties. Here's what I've found:
Default implementation is really good for calculated properties; it's not so good for read/write properties.
The code for this article is available on GitHub: jeremybytes/interfaces-in-csharp-8, specifically the InterfaceProperties project.

Note: this article uses C# 8 features, which are currently only implemented in .NET Core 3.0. For these samples, I used Visual Studio 16.3.0 and .NET Core 3.0.100.

Default Get
Let's start by looking at an interface that has a default "get" for a property. Here's an interface that describes a regular polygon and has a default implementation (from the IRegularPolygon.cs file):


This describes a regular polygon -- a geometric shape with 3 or more sides where each side is the same length.

The interface describes four properties. (1) NumberOfSides is a read-only property that tells how many sides the shape has. This is read-only because we do not want type of shape (triangle, square, etc.) to change after it is created.

(2) SideLength is a read/write property. The size of the shape is allowed to change during its lifetime.

(3) Perimeter is the distance around the shape. This is a calculated property. Since the calculation is the same for every shape, we included a default implementation.

This code uses the expression-bodied member syntax to return the number of sides multiplied by the side length. If you're not familiar with expression-bodied members, this is the equivalent of the following code:


The expression-bodied member syntax is a short-hand that removes the braces and the "return" keyword. I'll be using the shorter syntax throughout this article, but they are equivalent in functionality. Read more here: Expression-bodied members - Properties.

(4) Area is the space inside the shape. This is also a calculated property, but the calculation is different from each shape. Because of this, we do not provide a default implementation. It will be up to each class to specify it's own calculation.

A Shorter Default Get
There is a slightly shorter way to specify a default "get" for a property (from the IRegularPolygon.cs file):


This is a shorter version of the expression-bodied member syntax that can be used with read-only properties. In this case, we can get rid of the "get" keyword and the braces around that.

This syntax makes it a bit harder to tell whether this is a property or a method. (The difference is that an expression-bodied method would have empty parentheses after "Perimeter".) Read more here: Expression-bodied members - Read-only properties.

Implementing the Interface
Here is a square that implements this interface (in the SquareFromInterface.cs file):


This class implements the IRegularPolygon interface. It provides automatic properties for NumberOfSides and SideLength, and it has a constructor that initializes those properties. There is also an implementation for the Area property. This uses the same expression-bodied member syntax shown above.

One thing to note is that the Perimeter property is not implemented in this class. It relies on the default implementation provided by the interface.

Using the Class
This project is a console application, and we use this square class in the Program.cs file.


This code creates 2 instances of the SquareFromInterface class and displays the properties to the console. The output is unsurprising:


An important thing to note about this code is that accessing the "Perimeter" property from the ShowInterfacePolygon method works. Since the parameter (polygon) is IRegularPolygon, it uses the default implementation for Perimeter.

The Property is on the Interface Only
In this case, the concrete type, SquareFromInterface, does not have a Perimeter property. The property exists only as part of the interface. We can see this if we try to change the type of the "polygon" parameter to our concrete type:


In this code, the parameter is "SquareFromInterface". This type does not explicitly have the Perimeter property, so the code fails to build. (We can see the red squigglies under "Perimeter" in the code sample above.)

Overriding the Default Implementation
If we wanted to, the SquareFromInterface class could provide its own implementation of Perimeter. If it did provide that implementation, then that is the value that would be used, even if we cast the type to the interface. We'll take a closer look at how this works in a later article when we look at default implementation for methods.

Default Implementation for Read/Write Properties
There are some restrictions on default implementation for properties. When we have both a getter and a setter, we must provide implementations for both get/set or neither.

These examples are not valid (from the IBadInterface.cs file):


The first property has a default implementation for the get, but an abstract set (meaning, the implementing class needs to provide that). This is not allowed.

The second property has an abstract get with default implementation for the set. (The default implementation happens to be an empty method.) This is also not allowed.

For read/write properties, we need to provide default implementation for both get and set or for neither.

Note: when we provide default implementation for neither get nor set, we just end up with a regular abstract interface property like "SideLength" above.

Default Implementation for Set
Now that we've talked a little about read/write properties, the next question is whether default implementation makes sense for a read/write property, specifically how do we come up with a default implementation for "set".

No Automatic Implementation
Something to think about: there is no way for us to create an automatically implemented property (or auto property) as a default implementation. Let's look at this more closely. Here is the code from the SquareFromInterface class that we saw above:


Both NumberOfSides and SideLength are auto properties, meaning the compiler fills in some gaps for us. For example, the SideLength property really means this:


This is a property with a backing field. Since the "get" simply reads the backing field, and the "set" simply sets the backing field, this can be shortened to the auto property syntax that we have above. (As a side note, that is why the field is grayed-out; Visual Studio is telling us we can make this an auto property.)

The problem with automatic properties is that they use a backing field. This is instance data on the class. Interfaces are not allowed to have any instance data, so we cannot have backing fields in the interfaces.

This means that we can *not* create a property in an interface with an implementation that is an automatic property.

What Can We Put in the Setter?
The next question is whether there is anything that we can put into a default setter that would make sense. I'm not sure there is. (If you have any ideas, be sure to share them in the comments.)

Based on auto property syntax, it's tempting to try something like this for the setter (from the IBadInterface.cs file):


Unfortunately, this code compiles. It's tempting because it feels like we can cheat the auto property syntax for the setter. But in reality, it creates a Stack Overflow.

To show this, we implement this bad interface in an empty class (from the BadObject.cs file):


Since this object has no members, it uses the default implementation from the interface.

Here is some code to use that object (from the Program.cs file):


We can create the object with no problems. But if we try to set the BadMember property, bad things happen, specifically we get a Stack Overflow:



This isn't a surprise. When we set the "BadMember" property, it calls the setter which takes the value and uses it to set the "BadMember" property, which calls the setter for the "BadMember" property... You can see the problem.

Now this problem isn't any different from code that isn't using default implementation. If we create a property setter like this in a class, we will get the same Stack Overflow exception. The difference is that it's a bit more tempting to try something like that with default implementation in the interface to try to mimic what we would get from an automatic property.

Do Setters Make Sense?
Based on the fun that we've had here, I've wondered if there is a scenario where a default implementation for a property setter would make sense. So far, I haven't come across one. Be sure to share you ideas of you've got them.

Greenfield Abstract Class
If I was implementing the regular polygon scenario in a greenfield application (meaning, brand-new code), then I would probably use an abstract class. The implementation for the "Perimeter" calculated property would be just the same. But we could get some more safety for the NumberOfSides and SideLength properties.

Just to show the differences, I coded up the same functionality as an abstract class (from the AbstractRegularPolygon.cs file). First the NumberOfSides property:


The setter in this property has a guard clause to make sure that there are at least 3 sides to our polygon.

The SideLength property has a similar guard clause to make sure the length is greater than zero:


The rest of the class has a constructor to set the properties, and declarations for Perimeter and Area that look very similar to the interface:


The main difference is that for the abstract class, we mark Area as "abstract". We don't have to mark Area as "abstract" in the interface because that is the default.

Another Square Implementation
In the sample, we have a square class that inherits from the abstract class (from the SqureFromAbstractClass.cs file):


This class is a bit shorter than the one that implemented the interface. That's because it does not need to provide implementations for NumberOfSides or SideLength -- these come from the base class. The constructor calls the base class constructor with a hard-coded "4" for the number of sides.

Then we have an override of the Area property. This looks the same as our other square class.

Using the Square
The console application has code to use SquareFromAbstractClass (in the Program.cs file):


This code creates 2 instances from the interface class and 2 instances from the abstract class class. There is a different display function for the abstract class:


This is almost the same as the code that displays the interface properties. The only difference is the parameter type.

And we get similar output:


Since the display functions are so similar, it would be nice to combine them. In other samples, I've used "dynamic" to create a shareable method. But I ran into some problems when I tried that here.

Issues with Dynamic and Default Implementation
The regular polygon code is taken from a sample I use when teaching interfaces. For that code, I use a single method that can display multiple unrelated types. (The code is on GitHub if you're interested: DisplayPolygon sample.)

I ran into a problem when I tried that with the code that we have here. Here is a shareable method (from the Program.cs file):


This uses a "dynamic" as a parameter. This means that it is resolved at runtime rather than compile time. The good thing about this is that it will work with any object. And as long as the property or method is available on the object at runtime, everything works fine.

Or it almost works in this case.

Here's the code that calls the method:


But when using the dynamic method with the default property implementation, we get a runtime exception:


This is telling us that the runtime binder cannot find the "Perimeter" property on the "SquareFromInterface" object.

This is true. The "SquareFromInterface" class does not have its own "Perimeter" property. Instead, it relies on the default implementation from the interface.

You might be thinking that we need to pass to cast the object to the interface type before we pass it to the method. Well, it turns out that we did do that:


The "largeSquare" variable that we use is an IRegularPolygon (our interface). But even though we pass that type through, the runtime binder picks up the concrete object.

I don't know if this is a problem with the runtime binder, or whether this is intentional. I'll just leave a warning that when relying on a default implementation of an interface, there may be problems when combining it with "dynamic" objects.

Just as a note: the "dynamic" method works just fine with the abstract class class since "Perimeter" is part of the base class.

Update: I explored this behavior a bit further: C# 8 Interfaces: "dynamic" and Default Implementation.

Wrap Up
There has been a lot to think about. Default implementation seems like it can be really useful for calculated properties, particularly if we need to add a property after the interface is in a production environment.

But when it comes to setters for properties, default implementation doesn't seem to make much sense. And it's easy to get a stack overflow if we include a self-reference in the implementation.

As a reminder, you can get the code (and additional article links) from the GitHub repo: jeremybytes/interfaces-in-csharp-8.

We have a good idea of what the mechanics are for default implementation for properties. In future articles, we'll look at some guidance on default implementation in general.

Happy Coding!

No comments:

Post a Comment