Accessibility of nested classes
Happy New Year all and welcome to another year of having fabulous adventures in coding. I thought I'd start this year off by answering a question I get quite frequently:
I have a public outer class with a public nested class. The nested class represents a concept only meaningful in the context of the outer class, and it accesses private members of the outer class. I want some of the members of the nested class to be public to everyone, but I want some of them to be public only to the outer class; how can I do that?
Typically the members they want to be accessible only to the containing class to be things like property setters and constructors:
public class Outer{ public class Inner { // I want the ctor and setter to be accessible in Outer // but not to the general public. public Inner() { ... } public Foo Foo { get; set; } // whatever ... } public Inner Frob() { ... } // ... and so on ...}
I can think of four ways to solve this problem offhand, though I'm sure there are more.
Attempt one: my preferred solution is to deny the premise of the question and make the "private" details that need to be used by the container "internal". It is your assembly! If you don't want to abuse the internal details outside of a particular class, then don't do it. If your coworkers do it, educate them in code reviews. Not every authorial intention needs to be expressed in the accessibility system, after all.
Attempt two: again, deny the premise of the question, but this time make the nested type a private implementation detail. The received wisdom is that we want nested classes to be private implementation details of the public outer class, not themselves public classes. In that case you'd simply make an interface to expose the public details:
public interface IFoo{ public Foo Foo { get; }}public class Outer{ private class Inner : IFoo { public Inner() { ... } public Foo Foo { get; private set; } // whatever ... } public IFoo Frob() { ... } // ... and so on ...}
This has a couple of down sides though. We've denied the premise of the question, which is that the nested class makes sense to be public, and so not answered the question. We've also "polluted" the public namespace by adding a new IFoo concept unnecessarily. Also, there is now the possibility that just anyone can create an implementation of IFoo. Typically in these sorts of scenarios, the reason we want to restrict the constructor and setter is to ensure that the invariants of the inner type are being guaranteed by the code in the outer type.
Attempt three: did you know that it is legal for a type to implement an interface that is less public than the type? That is not true for base types, but it is true for interfaces!
public class Outer{ private interface IFooSetter { public Foo Foo { set; } } public class Inner : IFooSetter { public Inner() { ... } public Foo Foo { get; private set; } Foo IFooSetter.Foo { set { this.Foo = value; } } // whatever ... } public Inner Frob() { ... } // ... and so on ...}
Now code in Outer can cast an Inner to IFooSetter and call the setter that way, but code outside of Outer cannot cast to IFooSetter because that type is private to Outer.
This looks pretty good, but again we've got a few problems. We still haven't dealt with the constructor problem. And in both the solutions we've seen so far, it works poorly in the case where the inner type is a value type because we end up boxing. Yes, it is a very bad practice to make a mutable value type, but hey, that's probably why we're restricting the mutators to be only accessible from Outer! Regardless though, we'd like a way to avoid the boxing penalty.
Attempt four: avoid interfaces altogether and go straight to delegates:
// This code is wrong!public class Outer{ private static Func<Inner> innerFactory = null; private static Action<Inner, Foo> fooSetter = null; public class Inner { static Inner() { Outer.innerFactory = ()=>new Inner(); Outer.fooSetter = (i, f)=>i.Foo = f; } private Inner() { ... } public Foo Foo { get; private set; } // ... } public Inner Frob() => innerFactory(); // ... and so on ...}
Now code in Outer can call innerFactory() any time it needs an Inner, and can call fooSetter(inner, foo) any time it needs to call a setter on an Inner.
I'll leave you with two exercises:
- First, the code above is wrong because it can be easily made to crash; do you see how? Can you fix it? (Hint: there is no way using just the rules of C# that I know of; you need to use a special method created for compiler writers.)
- Second, suppose the inner type is a mutable value type; our setter would mutate a copy. Can you fix that too?