Monday, September 23, 2019

A Closer Look at C# 8 Interfaces

There are a lot of changes to interfaces in C# 8 -- not just default implementation. Last time, I had a bit of a rant about those changes. Now it's time to settle down and take a closer look at what those changes are and better understand them.

The short list of changes includes the following:
  • Default implementation
  • Access modifiers
  • Static members
And there are a lot of smaller things that come up once we start looking at these features.

This is too much to talk about in one article, so the next several articles will be looking at these items more closely. I've been looking at the preview versions of .NET Core 3.0 (the first environment to support these features). Now that .NET Core 3.0 has been released, we can dive into code to see what is there, what is possible, what the compiler allows, and what the runtime allows.

Default Implementation
Default implementation allows us to add implementation code to our interfaces. This means that we can provide method bodies for interface methods, events, indexers, and properties.

In looking at default implementation, there are a few questions that we will try to answer.

Property Get / Set
There are some things to explore about properties. For example, can you provide a default for a getter or setter separately, meaning a default implementation for a getter with an abstract setter? (The short answer is no.)

Update - Article now available: C# 8 Interfaces: Properties and Default Implementation.

Property Setter?
Since interfaces do not have any instance members (such as backing fields for properties), does it make sense to provide a default implementation for a setter? Is there a useful setter that we can provide?

Update - Article now available: C# 8 Interfaces: Properties and Default Implementation.

Generic Implementations
Would it be useful to create an interface that *only* has members with default implementation? If they have generic type parameters, this may be a way to add functionality to classes by only noting the interface and not changing the class itself.

Guidelines for Defaults
I've already seen an article with a really bad example of default implementation. The example makes assumptions about the concrete types that will be used and creates a scenario that would break implementations and make unit testing difficult.

Based on this, we need to take a look at what we put into default implementations to make sure that we keep a good environment for the folks using our interfaces.

Update - Article now available: C# 8 Interfaces: Dangerous Assumptions in Default Implementation.

Unit Testing
Now that we have code in our interfaces, how do we test it appropriately? On the surface, it seems like we can create a mock object and go. But some interesting things may pop up once we start looking at this more closely.

Update - Article now available: C# 8 Interfaces: Unit Testing Default Implementation.

Update: "dynamic"
In experimenting with default implementations, I found that "dynamic" does not find members that only have default implementations.

Article: C# 8 Interfaces: "dynamic" and Default Implementation.

Access Modifiers
Previously, interface members were automatically public. Now, members can have access modifiers including private, protected, internal, and public.

There are things that we will explore deeper in future articles.

Public Members
Public members really aren't a change since that's all we had before. But we need to take a look at how interface defaults differ from class defaults. For example, if we do not specify an access modifier on a class member, it defaults to private. But if we do not specify an access modifier on an interface member, it defaults to public. Not a huge deal, but we need to be aware of those differences.

Update - Article Now Available: C# 8 Interfaces: Public, Private, and Protected Members.

Private Members
Private members are new. These are not accessible outside of the interface itself. On the surface, this does not seem very useful for an interface since it is all about enforcing members on a class/struct. But private methods can be used by other members that have default implementation.

Private members *must* have a default implementation (because there is no way to add implementation outside of the interface itself).

Along with this, we need to take a look at what happens to our code once we start using private members. Will standard refactoring work? How does this affect testing? Can we treat this type of code (method extraction, small pieces, etc.) the same way we treat code in classes?

Update - Article Now Available: C# 8 Interfaces: Public, Private, and Protected Members.

Protected / Internal Members
Protected and internal members are also new, and we need to explore a bit further. What are the use cases for protected members? Are they useful only when deriving interfaces from other interfaces?

Also, how do these members impact the implementing classes? The short answer is that protected and internal members must be implemented explicitly in the class. The longer answer will depend on the use cases that we can come up with.

Update - Article Now Available: C# 8 Interfaces: Public, Private, and Protected Members.

Static Members
Another new feature is that we can have static members in interfaces. This includes static methods, static fields, and static constructors. These static members need to have implementation code in the interface.

Again, there is much to explore here, particularly around use cases and implementation.

Static Methods
What are the use cases for static methods in interfaces? I can imagine that we could have an interface that *only* has static methods. This could be a glob of functions that can be used independently of any class. How would this differ from a static class that only has static methods? There is a lot to explore here.

Static Fields
This one is really new since interfaces could not contain fields before. But the real restriction is that interfaces cannot have instance data. And that is still true. Instance fields are not allowed, but we can have static data at the interface / abstraction level.

There is a tutorial on the Microsoft docs site (https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/default-interface-members-versions) which shows how to use static fields as parameters for default implementation methods. This is an interesting use case. Are there other use cases for static fields?

Static Constructors
Interfaces can now have static constructors. What are the use cases for this? Static constructors can only access static members of the interface, so they could be useful for "new"ing up a static field (for example, a static field that is a List<T>).

Are there other uses for static constructors?

Other Features
There are a few other features that go along with the main items above. These are also worth a closer look.

Abstract Members
I previously wondered about the "abstract" modifier that is now allowed in interfaces. I didn't understand since interface members are "abstract" by default. But in looking into it further, "abstract" can be useful in an interface inheritance scenario.

If a base interface has a default implementation for a member, it is possible for a derived interface to mark that member as "abstract". This would have the effect of hiding the default implementation.

This gets interesting in the class implementations because a class that implements the inherited interface also implements the base interface. The class would need to implement the abstract member to satisfy the contract, but this is definitely worth taking a closer look at.

And just because this is possible doesn't mean we should do it. Are there good use cases for this?

Partial Interfaces
Previously "partial" interfaces were not allowed. Partial classes allow us to spread a class across different files. I have not seen widespread usage of this feature, but there are a few scenarios where it is useful. Interfaces now support this same "partial" functionality. It's worth taking a closer look at this to see what types of scenarios would need this.

Static Main
Another thing that I found in the design proposal is that "static Main" is supported in interfaces. I have not yet looked at this, but it will be interesting to explore further.

Struct and Default Implementation
There is a "wart" [terminology from the proposal] when it comes to structs and default method implementation. The interface methods are not available in the struct the same way they are in a class. So to get to the default implementation, it requires boxing the struct. I have not seen a lot of structs that implement interfaces. And guessing from the decision to leave it like this, I suspect that the language designers have not seen much of it either. This is more a curiosity than something you're likely to need to deal with.

Lots to Explore
That's my list of things that need further exploration. I'll be diving into these as soon as I get the release bits of .NET Core 3.0 and the latest Visual Studio 2019 updates.

If you have any ideas for use cases mentioned above, or there are other areas that you'd like me to take a closer look at, be sure to leave a comment.

Be sure to check back because there's lots to explore.

Happy Coding!

No comments:

Post a Comment