The curious case of the publicity-seeking interface and the shy abstract class

Noda Time has a guilty secret, and I’m not just talking about the fact that there’s been very little progress on it recently. (It’s not dead as a project – I have high hopes, when I can put some quality time into it.) This secret is called LocalInstant, and it’s a pain in the neck.

One of the nice things about giving talks about an API you’re currently writing is that you can see which concepts make sense to people, and which don’t – as well as seeing which concepts you’re able to explain and which you can’t. LocalInstant has been an awkward type to explain right from day 1, and I don’t think it’s improved much since then. For the purpose of this blog post, you don’t actually need to know what it means, but if you’re really interested, imagine that it’s like a time-zone-less date and time (such as "10:58 on July 2nd 2015" but also missing a calendar system, so you can’t really tell what the month is etc. The important point is that it’s not just time-zone-less, but it’s actually local – so it doesn’t represent a single instant in time. Unlike every other concept in Noda Time, I haven’t thought of any good analogy between LocalInstant and the real world.

Now, I don’t like having types I can’t describe easily, and I’d love to just get rid of it completely… but it’s actually an incredibly powerful concept to have in the library. Not for users of course, but for the implementation. It’s spattered all over the place. Okay, the next best step to removing it is to hide it away from consumers: let’s make it internal. Unfortunately, that doesn’t work either, because it’s referred to in interfaces all the time too. For example, almost every member of ICalendarSystem has LocalInstant as one of its parameters.

The rules around interfaces

Just to recap, every member of an interface – even an internal interface – is implicitly public. That causes some interesting restrictions. Firstly, every type referred to in a public interface must be public. So this would be invalid:

internal struct LocalInstant {}

// Doesn’t compile: Inconsistent accessibility
public interface ICalendarSystem

    LocalInstant GetLocalInstant(int year, int month, int day);
}

So far, so good. It’s entirely reasonable that a public member’s declaration shouldn’t refer to an internal type. Calling code wouldn’t understand what LocalInstant was, so how could it possibly use ICalendarSystem sensibly? But suppose we only wanted to declare the interface internally. That should be okay, right? Indeed, the compiler allows the following code:

internal struct LocalInstant {}

// Compiles with no problems
internal interface ICalendarSystem
{
    LocalInstant GetLocalInstant(int year, int month, int day);
}

But hang on… isn’t GetLocalInstant public? That’s what I said earlier, right? So we’re declaring a public member using an internal type… which we thought wasn’t allowed. Is this a compiler bug?

Well, no. My earlier claim that "a public member’s declaration shouldn’t refer to an internal type" isn’t nearly precise enough. The important aspect isn’t just whether the member is declared public – but its accessibility domain. In this case, the accessibility domain of ICalendarSystem.GetLocalInstant is only the assembly, which is why it’s a valid declaration.

However, life becomes fun when we try to implement ICalendarSystem in a public class. It’s perfectly valid for a public class to implement an internal interface, but we have some problems declaring the method implementing GetLocalInstant. We can’t make it a public method, because at that point its accessibility domain would be anything referring to the assembly, but the accessibility domain of LocalInstant itself would still only be the assembly. We can’t make it internal, because it’s implementing an interface member, which is public.

There is an alternative though: explicit interface implementation. That comes with all kinds of other interesting points, but it does at least compile:

internal struct LocalInstant {}

internal interface ICalendarSystem
{
    LocalInstant GetLocalInstant(int year, int month, int day);
}

public class GregorianCalendarSystem : ICalendarSystem
{
    // Has to be implemented explicitly
    LocalInstant ICalendarSystem.GetLocalInstant(int year, int month, int day);
    {
        // Implementation
    }
}

So, we’ve got somewhere at this point. We’ve managed to make a type used within an interface internal, but at the cost of making the interface itself internal, and requiring explicit interface implementation within any public classes implementing the interface.

That could potentially be useful in Noda Time, but it doesn’t solve our real LocalInstant / ICalendarSystem problem. We need ICalendarSystem to be public, because consumers need to be able to specify a calendar when they create an instance of ZonedDateTime or something similar. Interfaces are just too demanding in terms of publicity.

Fortunately, we have another option up our sleeves…

Abstract classes to the rescue!

I should come clean at this point and say that generally speaking, I’m an interface weenie. Whenever I need a reusable and testable abstraction, I reach for interfaces by default. I have a general bias against concrete inheritance, including abstract classes. I’m probably a little too harsh on them though… particularly as in this case they do everything I need them to.

In Noda Time, I definitely don’t need the ability to implement ICalendarSystem and derive from another concrete class… so making it a purely abstract class will be okay in those terms. Let’s see what happens when we try:

internal struct LocalInstant {} 

public abstract class CalendarSystem

    internal abstract LocalInstant GetLocalInstant(int year, int month, int day);


internal class GregorianCalendarSystem : CalendarSystem
{  
    internal override LocalInstant GetLocalInstant(int year, int month, int day)
    { 
        // Implementation
    } 
}

Hoorah! Now we’ve hidden away LocalInstant but left CalendarSystem public, just as we wanted to. We could make GregorianCalendarSystem public or not, as we felt like it. If we want to make any of CalendarSystem‘s abstract methods public, then we can do so provided they don’t require any internal types. There’s on interesting point though: types outside the assembly can’t derive from CalendarSystem. It’s a little bit as if the class only provided an internal constructor, but with a little bit more of an air of mystery… you can override every method you can actually see, and still get a compile-time error message like this:

OutsideCalendar.cs(1,14): error CS0534: ‘OutsideCalendar’ does not implement inherited abstract member
        ‘CalendarSystem.GetLocalInstant(int, int, int)’

I can just imagine the author of the other assembly thinking, "But I can’t even see that method! What is it? Where is it coming from?" Certainly a case where the documentation needs to be clear. Whereas it’s impossible to create an interface which is visible to the outside world but can’t be implemented externally, that’s precisely the situation we’ve reached here.

The abstract class is a little bit like an authentication token given by a single-sign-on system. From the outside, it’s an opaque item: you don’t know what’s in it or how it does its job… all you know is that you need to obtain it, and then you can use it to do other things. On the inside, it’s much richer – full of useful data and members.

Conclusion

Until recently, I hadn’t thought of using abstract classes like this. It would possibly be nice if we could use interfaces in the same way, effectively limiting the implementation to be in the declaring assembly, but letting the interface itself (and some members) be visible externally.

A bigger question is whether this is a good idea in terms of design anyway. If I do make LocalInstant internal, there will be a lot of interfaces which go the same way… or become completely internal. For example, the whole "fields" API of Noda Time could become an implementation detail, with suitable helper methods to fetch things like "how many days are there in the given month." The fields API is an elegant overall design, but it’s quite complicated considering the very limited situations in which most callers will use it.

I suspect I will try to go for this "reduced API" for v1, knowing that we can always make things more public later on… that way we give ourselves a bit more flexibility in terms of not having to get everything right first time within those APIs, too.

Part of me still feels uncomfortable with the level of hiding involved – I know other developers I respect deeply who hide as little as possible, for maximum flexibility – but I do like the idea of an API which is really simple to browse.

Aside from the concrete use case of Noda Time, this has proved an interesting exercise in terms of revisiting accessibility and the rules on what C# allows.

26 thoughts on “The curious case of the publicity-seeking interface and the shy abstract class”

  1. Why would you say a member on an internal interface is public? It’s clearly internal. You can’t access that member from outside the assembly.

    That’s like having

    internal class C {
    public void Member() { }
    }

    is C.Member here public? No, it’s internal.

    About interfaces, you *can* have interfaces with both public and internal members, using ugly interface inheritance.

    public interface ICalendarSystem {
    public methods;
    }

    internal interface ICalendarSystemInternal {
    LocalInstant GetLocalInstant(int year, int month, int day);
    }

    Of course, that does come with a pretty steep price – you’d have to check that the calendar system is an internal one every time and you’ve basically lost any type safety.

  2. @configurator: No, it’s clearly public. Look in the IL, look at the spec. They’re both very explicit about it. Your C.Member member is public, but its accessibility domain is the assembly. There’s a difference, and it’s relevant.

  3. Actually I think it’s OK to expose this type publicly, even if you don’t have an easy way to explain what it does. Even though most users won’t use it, it will still be available to advanced users who do understand how the library works internally.

    The .NET framework has a lot of public types that come with this description:

    “This API supports the .NET Framework infrastructure and is not intended to be used directly from your code”

    I think you could do something similar with LocalInstant…

  4. “I know other developers I respect deeply who hide as little as possible, for maximum flexibility”

    Hmmm. Now, I would have said that hiding as much as possible gives you the most flexibility.

    It depends what you want to be flexible about. If you hide as much as possible, especially in a framework whose use you don’t control, you will have much more flexibility to change it later. The more you expose, the more brittle to change it becomes.

    Also, I have for many years railed against this obsession with interfaces many developers have. You are almost always better off starting with a class (abstract or not). The reason is reversibility. When you discover a problem with your design, it’s easy to extract an interface from a class (and it’s generally obvious when it’s a good idea), but it’s not so easy to go the other way.

    I believe interfaces are another one of these “last responsible moment” things. Waiting for the right interfaces to fall out of the design is often better than just throwing one in whenever you think one might be required.

    And I don’t like Thomas’ suggestion. Just because MS do it doesn’t make it a good idea.

  5. @Jim: Hiding everything gives the *implementor* flexibility later on. Exposing everything gives the *caller* flexibility. Sorry for not being clear.

    I’m still generally in favour of interfaces as a “purer” expression of an abstraction, but I think they’ll win me over in this case.

    I *do* definitely disagree with Thomas, in that I really don’t like having bits of the API which I effectively have to say “Look but don’t touch” about. It also makes the API harder to explore, when there’s more of it.

    All this disagreement is very healthy though, I think :)

  6. I’ve used the ‘internal abstract’ technique a lot over the years. I tend towards trying to hide as much as possible, as shipping an API to third-party users means committing to a contract: you want that contract to be as narrow and precise as you can make it, yet still get the job done.

    As I said in an earlier tweet, it comes in useful for class hierarchies that are not open to be derived from in third-party code; and this actually happens quite often when writing APIs. I often use polymorphism for interface-level assignment compatibility purposes, but not for third-party reuse. An example might be in a reflection mechanism: Member might have a name and visibility, while Method deriving from Member may introduce Parameters and ReturnType, etc. But a reflection mechanism (e.g. built into a langauge’s RTL) isn’t generally meant to be derived from by third parties, as the language isn’t usually extensible by third parties. Thus internal abstract methods (or equivalents) help with creating a nice API, but being able to communicate effectively behind the scenes to get the functionality working.

  7. Other thing: as to interfaces as “purer” expressions of an abstraction, I agree, but only if the abstraction definitely is pure. Since there’s no way to change the interface after it’s been shipped, you have to be absolutely certain of the abstraction being modeled. If in doubt, then use a class.

  8. All right, I must be missing something big here. What does it matter if the member is public or internal in an internal class? What’s the difference, and why is it relevant?

  9. The last code snippet carried over the “Has to be implemented explicitly” comment when it no longer applies. (Clearly there’s a rant about the merits of comments hiding here.)

  10. @configurator: The problem is that when you override a method (or implement an interface method) it has to be as public as the original declaration. So you can’t implicitly implement an internal interface method with an internal method, which is what you probably want to do.

    @Blake: Oops, thanks :) Fixed.

  11. As I understand it, the problem at hand is not the exposure of the LocalInstant type per se, but rather that it is hard to explain? In that case perhaps it would be sufficient to find a better name for the type, and leave it exposed (which is not necessarily easier).

    The name RelativeInstant pops up in my head; it describes an instant that is defined only in relation to some other non-relative instant. Not sure that would make things more clear though.

    Either way, interesting use of abstract classes (that I often feel is a feature that doesn’t get the love it deserves).

  12. @Fredrik: Well, I don’t want to expose any types that the caller doesn’t need to use, ideally… and I don’t *think* they really ought to be thinking in terms of LocalInstant. Whatever the name is, I’d have to describe it in words – and that’s proved extremely difficult :(

  13. Right, you can’t implicitly implement an internal interface member with an internal member. But you can explicitly, and that’s just semantics (although I don’t like explicit implementation syntax I don’t think it really matters).

    Anything else that’s different between the members being public or internal?

  14. @configurator: “That’s just semantics” always seems a strange phrase to me. What’s the difference between a cat and a data-center? Just semantics. The two words just mean different things, that’s all – semantics.

    Explicit interface implementation comes with some odd bits and pieces, including making it awkward to call a method from the same class, and making it impossible to override the member in a derived class.

  15. Another option could be to nest the LocalInstant class in the abstract base class and make it protected internal. That would enable anyone inheriting from the base class to use it while keeping it hidden during normal use.

  16. Hi!

    Fun post, it’s a long time since I’ve pondered about the different impacts of visibility between abstractions.

    I think it is good that Noda now will scale down and focus on getting something out there that will satisfy most users. Most users use standard .Net 2 or above, they use the System.DateTime type and they are familiar with the methods exposed by this class. I also think that most users have never used Joda, and is not familiar with the Joda API.

    Thus at least I think it is natural to avoid transelaring the API directly, but rather rethink how the API should be exposed to give developers a feeling of using their standard DateTime implementation but with calendars and time zone support. I think that this is critical to make Noda a popular choice in the .Net community.

    Good luck,
    Steianr.

  17. If your public abstract class CalendarSystem has an internal abstract method, then you should probably make all its constructors abstract as well. That makes it very obvious that CalendarSystem can’t be derived from by classes outside the assembly. I don’t think making the constructors internal would have any nasty side effects.

  18. Sorry, I meant to say ‘make its constructors internal’. Obviously constructors can’t be abstract.

  19. I’m a bit surprised you need to resort to base classes, I was thinking a public interface could derive from an internal base interface (which you could have used to hold the internal interface methods), but I’ve just tried this and found that the compiler rejects it. Why? Classes are allowed to do the same thing after all.

  20. @Freed: No, classes aren’t allowed to do the same thing – you can’t derive a public class from an internal one.

  21. And how about going with two interfaces?
    like so:
    ICalendarSystemImplementation : ICalendarSystem

    Where the first one is internal and explicitly implemented and the second one is public.

    Oh, now that I see the comments, is what Freed says but inverted, I think it works.

  22. Although, thinking it through a little more.. that won’t allow you to just take an ICalendarSystem and (internally) reference some method that uses LocalInstant.. some downcasting to ICalendarSystemImplementation will probably have to be involved, uglier.. but it may still be an option to consider though.

Comments are closed.