Saturday, November 9, 2019

C# 8 Interfaces: Public, Private, and Protected Members

When taking a closer look at C# 8 interfaces, we come across an interesting change: interface members can have access modifiers - public, private, and protected (and others).
Public members are what we are used to. Private members are useful for refactoring default implementations. Protected members may be useful for interface inheritance (but not much else).
Let's take a look at a few examples to show what we can and can't do with access modifiers and interface members.

The code for this article is available on GitHub: jeremybytes/interfaces-in-csharp-8.

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.6 and .NET Core 3.0.100.

Public Interface Members
Prior to C# 8, interface members were public by default. In fact, if you put an access modifier on an interface member (including public), it would generate a compiler error.

The code for this section is available in the "Public" folder of the "AccessModifiers" project on GitHub.

Interface Members Default to "public"
In C# 8, interface members are still public by default. But since other access modifiers are allowed (as we'll see in a bit), public is also allowed.

In the following code, both of the interface members are "public" (from the ICustomerReader.cs file on the AccessModifiers project).


Both the "GetCustomers" method and the "GetCustumer" method are public. So whether we use the "public" keyword or no access modifier, the members of an interface are public.

Class Members Default to "private"
Classes in C# behave differently. Class members default to "private". Here is a sample of a "Customer" class with private members (from the Customer.cs file):


This class has 2 private fields: _familyName and _givenName. Neither of these fields are accessible outside of the class.

The main reason to point this out is that default access is different for interface members (public) and class members (private).
As a practice going forward, I will use "public" explicitly in my interfaces to reduce confusion.
Implementation Still Needs to be Marked "public"
Something important to note is that even though the interface members default to public, they still need to be explicitly marked as "public" in the implementation code.

Here is a snippet of the "FakeCustomerReader" class that implements the "ICustomerReader" interface that we saw above (from the FakeCustomerReader.cs file):


This code provides implementations for the "GetCustomer" and "GetCustomers" methods from the interface. But they both must be marked as "public" for the code to compile.

Even though "GetCustomers" does not have an access modifier in the interface (public default), it does require an access modifier in the implementation since classes default to private.

This is no different from interface implementations prior to C# 8. Even though interface members did not have access modifiers, the implementations required "public" implementation (unless they were explicit implementations -- we'll talk a little about explicit implementation further down).

Private Interface Members
Something new in C# 8 is that we can now have private interface members. Let's work through the why and how of private members by looking at some code.

The code for this section is available in the "Private" folder of the "AccessModifiers" project on GitHub.

Private Members Must Have Default Implementation
By definition, private members are not accessible outside of the current object. In our case, this means that a private interface member is not accessible from outside of the interface.

Consequently, there is no way to provide an implementation for a private interface member outside of the interface.

Fortunately, the compiler knows about this. The following code is not allowed (from the ICalendarItem.cs file):


In this code, we try to create a private read-only property for the interface.

Here is the error message:


This tells us that we *must* declare a body (i.e., a default implementation) for this member. (Note: we won't get into the "abstract, extern, or partial" in this article. We'll look at those in future articles.)

So to make our code happy, we must provide a default implementation (from the ICalendarItem.cs file):


This adds a default implementation to the "DefaultType" property.

As a side note, "CalendarItemType" is an enum defined in the ICalendarItem.cs file.


Private Members are Useful for Other Default Implementations
Since private members are not accessible from outside of the interface itself, they are really only useful for default implementations of other interface members.

Here's an overly-simplified example (from the ICalendarItem.cs file):


Here we have another interface member: a public property called "ItemType". This has a default implementation that uses the private "DefaultType" property.

Again, this is an overly-simplified example. The primary purpose that I've seen for using private members on interfaces is to extract methods and refactor code to smaller pieces.

Here's the problem that I have with those examples: if the code in the interface implementation is so large that we feel the need to refactor, then is this really an interface? At some point we will need to decide whether we keep interfaces as abstractions or turn them into active code files.

In my current opinion (which is subject to change), if we add too much code to interfaces, then they are no longer "interfaces". I really wish that these new features were added under a new name or syntax. At some point the word "interface" becomes meaningless. But that's a discussion for another time.

Private Interface Properties are Read-Only
In thinking about private members, we should consider what we learned about properties and default implementation. In a previous article (Properties and Default Implementation), we saw that default implementation does not make sense for read-write properties. It is good for calculated or constant read-only properties.

Since private interface members must have default implementation, we can extend this to mean that private interface properties should also be read-only.

This isn't really ground-breaking, just another thought that came up while I was exploring code.

Private Interface Members are not Accessible by Implementing Classes
As stated above, private interface members are not accessible from outside of the interface itself. This means that an implementing class cannot access private members (which should not be a surprise).

Just to show this, here is some code from a "CalendarEvent" class that tries to access the private member (from the CalendarEvent.cs file):


This code tries to assign to the private "DefaultType" member of the interface.

The error message tells us that "DefaultType does not exist in the current context."


This is the same message that we would get when trying to access a private member on any other type.

Protected Interface Members
The last thing that we'll look at today is protected interface members. This is the strangest (and probably least useful) of the access modifiers here.

The code for this section is available in the "Protected" folder of the "AccessModifiers" project on GitHub.

I'm not sure how this works completely. So the examples here are from my experimentation. In the language proposal for default interface methods, the issue is left open:


"Open Issue: We need to specify the precise meaning of the access modifiers such as protected and internal, and which declarations do and do not override them (in a derived interface) or implement them (in a class that implements the interface)."

There may be a GitHub issue that resolves this; however, I haven't had any luck finding one so far. Here's what I've found through experimentation.

Protected Interface Members are Allowed
As an experiment, I created an interface that has a protected member (from the IInventoryController.cs file):


This interface has 2 members: a public "PushInventoryItem" method and a protected "PullInventoryItem" method.

Protected Interface Members *Must* be Implemented Explicitly
The next thing I found is that protected interface members must be implemented explicitly.

If we try to implement a protected member "normally", we get an error. Here's code for a "FakeInventoryController" (from the FakeInventoryController.cs file):


This has implementations for both the public member and the protected member, but the red squigglies under the interface name tell us that something is wrong.


The error message tells us that we have an incomplete implementation of the interface: "FakeInventoryController does not implement interface member PullInventoryItem".

Changing the access modifier on the affected item does not change anything. The only way to satisfy the interface is to implement the interface member explicitly.

Here is the explicit implementation (from the FakeInventoryController.cs file):


With explicit implementation, we do not provide an access modifier on the implementation. In addition, the declaration "IInventoryController.PullInventoryItem" specifies that this code can only be called using the explicit interface type. (We'll see this in just a bit.)

To show the differences between the public and protected members of the interface, the final code shows both interface members implemented explicitly (from the FakeInventoryController.cs file):


This code compiles without errors.

There is No Way to Access the Protected Interface Implementation
This is where things get really strange. We have an interface with a protected member (this compiles fine). We have a class that explicitly implements that interface (this compiles fine). But we can *not* access the protected implementation.

The root of the AccessModifiers project on GitHub has a Program.cs file to test this.


This code creates an instance of the "FakeInventoryController" class and assigns it to an "IInventoryController" variable.

Note that it is important that the variable is the interface type. When using explicit implementation, we can only access the interface members through the interface type. (For more information on explicit implementation, take a look at this article: Explicit Interface Implementation.)

When we access the "PushInventoryItem" method (the public interface member), everything works as expected.

But when we access the "PullInventoryItem" method (the protected interface member), we get a compiler error.


The error tells us that "PullInventoryItem is inaccessible due to its protection level". So even though we have access to the member through the class (and the explicit implementation), we cannot call that implementation. This is true even though we are using the interface as the type.

Using Protected Interface Members
If we can't use protected interface members, then what are they there for? As noted in the "Open Issue" that we saw above, protected members may be useful when we have interface hierarchies (meaning, one interface inherits from another interface).

I haven't done any experiments with this because I try to keep things simple. I do use interface inheritance, but I do this to compose interfaces and add new members. I do not do this to try to override base behavior or get into polymorphism. I've generally found that if you try to be to clever with polymorphism and determine which method gets called depending on the situation, it's very difficult for the humans to follow along.

So I'll leave this as an exercise for the reader.

Broken Tooling with Protected Members
Visual Studio tooling is generally really awesome, but the current tooling breaks down with protected interface members (at least with Visual Studio 2019 16.3.6).

I love to use the Quick Actions in Visual Studio (particularly to implement interfaces). You get to this by clicking the lightbulb icon or by using "Ctrl+." with the cursor on the interface.

When implementing an interface with these tools, the explicit implementation works just fine:


Using "Implement interface explicitly" generates both methods as explicit implementations (which is what we have in the final code).

But if we try to use "Implement interface" (the non-explicit version), we run into problems:


This only generates implementation for one of the members (the public one).

And as we saw earlier, this leads to an incomplete implementation:


Note the red squigglies that we saw earlier. This is because we do not have an implementation for the protected member.

The reason that I'm a bit disappointed by this is because Visual Studio has been really smart with implementations in the past. For example, if there were conflicting interface members (meaning 2 interfaces with the same method name and parameters, but different return types), Visual Studio would implement the conflicting members explicitly (even if you asked for the non-explicit implementation).

I'm hoping that this will be fixed in a future update.

Wrap Up
Interfaces in C# 8 have changed significantly. Adding access modifiers creates a lot of interesting things to thing about.

"Public" is still the default, and this is what we should use most of the time. As mentioned, I've started explicitly using the "public" keyword to reduce confusion.

"Private" is useful for members that will be used for other default implementations. This is a way to break up the code into smaller pieces. (As noted above, whether our interfaces should get this complex is something to consider.)

"Protected" is there, but not very useful. It may be useful in interface hierarchies, but it cannot be used directly through explicit implementation.

There's lots to think about. To get the code and links to other articles, be sure to visit the GitHub repo: https://github.com/jeremybytes/interfaces-in-csharp-8.

Happy Coding!

No comments:

Post a Comment