2

I have the following code that does not compile:

public class Outer
  {
    public Inner MyField = new Inner(); //error here: "field type is less accessible than field"
    private class Inner
    {
        public string Message = "Hello";
    }
  }

I must be able to use the class like so:

var MyObject = new Outer();
Console.WriteLine(MyObject.MyField.Message); //should output "Hello"

"Inner" must ONLY be instantiable from within "Outer", so this should NOT be allowed:

var MyObject = new Outer.Inner(); //I do not want users to be able to instantiate "Inner" directly
taquion
  • 2,667
  • 2
  • 18
  • 29
user3163495
  • 2,425
  • 2
  • 26
  • 43
  • 4
    Make `Inner` public. – Igor Mar 05 '18 at 21:58
  • 1
    What you're trying to do doesn't make sense, you may be looking at it the wrong way. How would a user even access that value if they don't have access to its type? – Broots Waymb Mar 05 '18 at 21:58
  • 2
    What are you trying to achieve by doing that ? Is that just some C# brainteaser or does it reflects some actual other needs in your application? – Pac0 Mar 05 '18 at 21:59
  • 5
    public inner classes are generally considered a code smell. not always. – Zohar Peled Mar 05 '18 at 21:59
  • @Pac0 I made an edit to my question to be more clear – user3163495 Mar 05 '18 at 22:06
  • `new Outer.Inner(); //should throw an error` <= why? – Igor Mar 05 '18 at 22:08
  • @Igor because I don't want other users to be able to instantiate "Inner" directly, for encapsulation reasons – user3163495 Mar 05 '18 at 22:08
  • 1
    @user3163495 then you just need to provide a private constructor. And use some kind of Singleton / Factory pattern. (and / or by using interfaces as an answer below) The fact that you create a private class means that noone is supposed to see this class. Here you apparently want the class to be known by others, just not instantiated. – Pac0 Mar 05 '18 at 22:11
  • 1
    Had the very same requirement before. The answer is [here](https://stackoverflow.com/q/1664793/4430204) – taquion Mar 05 '18 at 22:11
  • But why do you care if they create an instance or not? As for encapsulation I agree that is important but that does not mean hide constructors of all your types and make types nested. A better example of how to encapsulate would be to use a property with a private setter instead of a field for `MyField`. – Igor Mar 05 '18 at 22:12
  • In the linked question I added my own answer for generic private clases too =) – taquion Mar 05 '18 at 22:12
  • Possible duplicate of [How to restrict access to nested class member to enclosing class?](https://stackoverflow.com/questions/1664793/how-to-restrict-access-to-nested-class-member-to-enclosing-class) – Pac0 Mar 05 '18 at 22:15
  • 1
    Why is class `Inner` a nested class in the first place? Does it need access to the private members of `Outer`? – Eric Lippert Mar 05 '18 at 22:17
  • 1
    Why must `Inner` only be instantiable from inside `Outer`? Do you not trust your coworkers to use your class correctly? – Eric Lippert Mar 05 '18 at 22:18
  • Trusting is good, but not trusting is better =) (just a joke) – taquion Mar 05 '18 at 22:19
  • Why are `MyField` and `Message` public fields? Public fields are usually thought of as a bad practice. – Eric Lippert Mar 05 '18 at 22:20

2 Answers2

10

The typical way to solve this is via an interface:

public class Outer
{
    public IInner Inner = new Inner();
    public interface IInner { ... }

    private class Inner: IInner { ... }
}

IInner need not be nested, any choice is viable.

An interesting variation of this pattern is when the nested classes inherit from the outer class. This is a pretty handy code structure that allows really elegant solutions:

public abstract class Outer
{
     public static Outer GetOuter(...)
     {
         if (someConditionMet) return new InnerSpecialized1();
         return new InnerSpecialized2();
     } 

     private Outer() { ... } //avoids anyone extending Outer

     private class InnerSpecialized1: Outer { ... }
     private class InnerSpecialized2: Outer { ... }
}
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • 1
    The pattern in the second code sample is one of my favourites. – Eric Lippert Mar 05 '18 at 22:20
  • @EricLippert I seem to recall reading one of your blogs where you use it in a really cool way but I can’t find it now. – InBetween Mar 06 '18 at 07:14
  • @EricLippert Just for reference and general knowledge, does this pattern has (a) commonly used name ? – Pac0 Mar 06 '18 at 07:37
  • @InBetween: I don't think I've blogged about it but I have mentioned this pattern in other SO answers on this topic. – Eric Lippert Mar 06 '18 at 14:47
  • @Pac0: I don't know of a common name for this pattern. I call it the "nested class factory pattern", but that's just me. – Eric Lippert Mar 06 '18 at 14:47
  • @InBetween How would I access, say, a member of InnerSpecialized1 if InnerSpecialized1 is private? This does not work: var MyObj = Outer.GetOuter(); Console.WriteLine(MyObj.SomeMember); – user3163495 Mar 06 '18 at 16:46
  • @user3163495 the public surface is defined by the outer class. The nested classes either override behavior or implement abstract members. The consumer sees only one class; `Outer`, he knows nothing about the private nested classes. – InBetween Mar 06 '18 at 17:05
  • @EricLippert I was slighlty mistaken (this is vintage stuff...we're getting old). You do use a very similar pattern in one of your series; outer and inner classes all implement a common interface: [Immutability in C# Part Three: A Covariant Immutable Stack](https://blogs.msdn.microsoft.com/ericlippert/2007/12/06/immutability-in-c-part-three-a-covariant-immutable-stack/) – InBetween Mar 07 '18 at 08:58
  • @InBetween: Good find! I had forgotten that I'd used that pattern there. – Eric Lippert Mar 07 '18 at 14:14
2

You need to expose the field's getter only and construct the instance within the class:

public class Outer
{
    public Outer()
    {
        MyField = new MyField();
    }
    public Inner MyField {get; private set;}
}

public class Inner
{        
    internal Inner()
    {
    }
    public string Message = "Hello";
}
JuanR
  • 7,405
  • 1
  • 19
  • 30
  • internal just limits callers of the constructor to being in the same assembly – Dave Mar 05 '18 at 22:22
  • @Dave: And only your coworkers can access it. If you coworkers are abusing your code then take that up with them *in code review*. That's what code review is for. – Eric Lippert Mar 05 '18 at 22:23