3

Goal

My goal is to implement a sealed, public nested class that can only be created by its enclosing class - without using reflection.

That means that the nested class cannot have any public or internal constructors or any public or internal static factory methods.

Previous Work

This post from a couple of years ago seems to be the answer. (That entire thread has a lot of information about what I'm trying to achieve.)

How it works is quite straightforward: It leverages the fact that a nested class can access the static fields of its enclosing class, along with a static constructor for the nested class.

The enclosing class declares a static Func<NestedClassType, NestedClassCtorArgType> delegate which returns an instance of the nested class, so that the enclosing class can use that delegate as a factory method.

The nested class itself has a static constructor which initialises the enclosing class's static factory delegate to a delegate that will create an instance of the nested class.

The Problem

Unfortunately, I can't get it to work as it is written in that answer. The reason is that the static constructor for the nested class is not called before the enclosing class uses the factory method, and thus there is a null-reference exception. (If you look at the sample program at the end of this question you will see what I mean.)

My Workaround

I have worked around the problem as follows:

  1. Added to the nested class an internal static Initialise() method that does nothing.
  2. Added to the enclosing class a static constructor that calls the nested class's Initialise() method.

This works fine, but it leaves a bit of a carbuncle in the shape of an internal static void Initialise() method.

My Questions

Is there a way to avoid this way of doing it? I can't help but think I'm missing something from the original post that I linked above. Did I misunderstand the answer?

Is there a clever way to force the static constructor for the nested class to run before I call the code that tries to create an instance of the nested class?

And are there any other problems with this approach?

(I'm aware that I can just write a public interface for the nested class, and return that instead. This question isn't about solving it that way!)

The Sample Code

Here's my sample code. Try running it, and it will print "Test". Then try commenting-out the line that is marked <--- If you comment this out, things won't work and run it again.

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            Outer outer = new Outer();
            Outer.Inner item = outer.Item("Test");
            Console.WriteLine(item.Text);
        }
    }

    public sealed class Outer
    {
        public Inner Item(string text)
        {
            return _nestedFactory(text);
        }

        // This static constructor calls the nested class's Initialise() method, which causes the
        // nested class's static constructor to run, which then sets the enclosing class's 
        // _nestedFactory field appropriately.

        static Outer()
        {
            Inner.Initialise(); // <--- If you comment this out, things won't work.
        }

        // This class has a private constructor.
        // I only want class Outer to be able to create instances of it.

        public sealed class Inner
        {
            private Inner(string value) // Secret private constructor!
            {
                text = value;
            }

            public string Text { get { return text; } }

            static Inner()
            {
                _nestedFactory = text => new Inner(text);
            }

            internal static void Initialise(){}
            readonly string text;
        }

        static Func<string, Inner> _nestedFactory;
    }
}
Community
  • 1
  • 1
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    If you are willing to have an `internal Initialise()` method, why the reluctance to use an `internal` constructor? That would seem a far simpler solution to the problem... – Marc Gravell May 02 '13 at 13:33
  • 1
    @MarcGravell The internal initialise method doesn't do anything from the caller's point of view - it doesn't create anything. Whereas an internal constructor does create something; I think those two things are quite different. – Matthew Watson May 02 '13 at 13:36
  • @Matthew then let me ask: why the reluctance to use `internal` here? – Marc Gravell May 02 '13 at 13:36
  • @MarcGravell I actually am happy to use `internal` for the `Initialise` method, but I'd like to know if there is a way that makes the simpler approach work - and if you read the original answer that I linked, it's got a lot of upvotes which makes it look like it works, so I wonder if I have misunderstood it. I realise that this is somewhat of an academic exercise, but I often learn a lot from academic exercises. :) – Matthew Watson May 02 '13 at 13:39
  • 1
    I am curious as to the scenario that requires this kind of class design. Could you elaborate a bit on this? – Daniel Hilgarth May 02 '13 at 13:47
  • @DanielHilgarth Nothing I'm doing *requires* such a strict interface; I just like to hide stuff that other classes shouldn't be concerned with. Normally, you just use interfaces to do so; in my case however I'm returning instances of an immutable class that I require to be immutable because it is being shared - and of course, interfaces do not provide a guarantee to the consuming code that the instance is really immutable (but a sealed concrete class does provide such a guarantee, unless you mess around with reflection). – Matthew Watson May 02 '13 at 14:35
  • @DanielHilgarth (Continued from previous comment) So basically, I just want to prevent client code from creating instances of a class. I think that's a relatively reasonable thing to want to do... – Matthew Watson May 02 '13 at 14:37
  • @MatthewWatson: It sure is. You just normally don't have client code in the same assembly than the code the client consumes. That's why I was asking. – Daniel Hilgarth May 02 '13 at 14:41
  • @DanielHilgarth That's very true; I guess I'm taking the data hiding to somewhat of an extreme. The class in question in this case is actually used in the same assembly though - it is in fact internal, but I still didn't want the other code in the same assembly to be able to create it. The assembly isn't even particularly large (if it was, I'd split it up). I guess it probably really isn't worth the extra effort at the end of the day; but I wanted to try this out anyway. I'll probably not really use it much in the future, it at all. – Matthew Watson May 02 '13 at 14:46
  • @MatthewWatson: Thanks for the clarification. In my eyes there is nothing wrong with the reason why you want to achieve what you are trying to achieve. I was just asking to find out if the reason was something else. Something that could have been solved by other means. But it really is just you being pendantic about the visibility - which isn't a bad thing :) – Daniel Hilgarth May 02 '13 at 14:53

3 Answers3

3

C# doesn't have "friend" functions. One pragmatic approach might be to simply ask the caller to prove who they are. One way to do this might be by supplying an object-reference that only the legitimate calling class could know, i.e. an object that is private to the outer-class. Assuming you don't hand that object out, only way to get around that would be non-public reflection, and if you need to protect against non-public reflection then the scenario is moot, because anyone with access to non-public reflection can already access things like a private constructor. So something like:

class Outer {
    // don't pass this reference outside of Outer
    private static readonly object token = new object();

    public sealed class Inner {
        // .ctor demands proof of who the caller is
        internal Inner(object token) {
            if (token != Outer.token) {
                throw new InvalidOperationException(
                    "Seriously, don't do that! Or I'll tell!");
            }
            // ...
        } 
    }

    // the outer-class is allowed to create instances...
    private Inner Create() {
        return new Inner(token);
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks. Yes, I am an ex-C++ programmer, and the `friend` keyword is exactly how I used to solve this particular issue! This Token idea is quite a nice approach though. It does move it from compile-time detection to run-time detection, which is a slight drawback. – Matthew Watson May 02 '13 at 13:44
  • @MatthewWatson indeed, it has the benefit of being simple and obvious, even to the compiler - without the need to worry about when static constructors get invoked, which is a really tricky question to answer accurately, and which actually changes between .NET versions (which might actually make older answers that *depend* on side-effects of the static constructor *invalid* on, say, .NET 4.5). As for run-time detection... yeah, tricky – Marc Gravell May 02 '13 at 13:49
3

You can use this code if you need to force a class constructor to run without having a reference to the type:

static Outer()
{
    System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (Inner).TypeHandle);
}
Vyacheslav Volkov
  • 4,592
  • 1
  • 20
  • 20
  • That does work. I just needed to replace the line of code `Inner.Initialise()` in the Outer class's static constructor with the line of code you gave me. Then I could remove the Inner class's `Initialise()` method Nice. :) Plus, calling this explicitly like this will ensure that I'm not dependent on any changes in the undocumented order of static class initialisation. Incidentally, this isn't entirely academic - I'm using this in real code now. – Matthew Watson May 02 '13 at 13:54
1

I also used static constructors to strictly control the accessibility of the nested class and ended up with similar overcomplicated code. But I eventually came up with a simple and clear solution. The foundation is explicit implementation of a private interface. (No static constructors)

    public sealed class Outer
    {
        private interface IInnerFactory
        {
            Inner CreateInner(string text);
        }

        private static IInnerFactory InnerFactory = new Inner.Factory();

        public Inner Item(string text)
        {
            return InnerFactory.CreateInner(text);
        }

        public sealed class Inner
        {
            public class Factory : IInnerFactory
            {
                Inner IInnerFactory.CreateInner(string text)
                {
                    return new Inner(text);
                }
            }

            private Inner(string value) 
            {
                text = value;
            }

            public string Text { get { return text; } }
            readonly string text;
        }
    }
}

This solution also gives compile time safety. Although Outer.Inner.Factory can be instantiated outside of outer, the method CreateInner can be called only through the IInnerFactory interface which means only in Outer. The following lines won't compile (with different errors) outside, even in the same assembly:

    new Outer.Inner.Factory().CreateInner("");
    ((Outer.IInnerFactory)new Outer.Inner.Factory()).CreateInner("");
lisp
  • 4,138
  • 2
  • 26
  • 43