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;
    }
}
有帮助吗?

解决方案

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);
}

其他提示

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);
    }
}

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("");
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top