如何实现只能由其封闭类创建的密封的公共嵌套类

本文关键字:创建 密封 嵌套 何实现 实现 | 更新日期: 2023-09-27 18:11:54

目标

我的目标是实现一个密封的公共嵌套类,该类只能由其封闭类创建,而不使用反射。

这意味着嵌套类不能有任何公共或内部构造函数,也不能有任何公用或内部静态工厂方法。

以前的工作

这篇几年前的帖子似乎就是答案。(整个线程有很多关于我试图实现的目标的信息。(

它的工作方式非常简单:它利用了嵌套类可以访问其封闭类的静态字段的事实,以及嵌套类的静态构造函数。

封闭类声明一个静态Func<NestedClassType, NestedClassCtorArgType>委托,该委托返回嵌套类的一个实例,以便封闭类可以将该委托用作工厂方法。

嵌套类本身有一个静态构造函数,它将封闭类的静态工厂委托初始化为一个将创建嵌套类实例的委托。

问题

不幸的是,我无法让它发挥作用,因为它写在答案中。原因是嵌套类的静态构造函数在封闭类使用工厂方法之前被调用,因此存在null引用异常。(如果你看看这个问题末尾的示例程序,你就会明白我的意思。(

我的解决方案

我已经解决了以下问题:

  1. 向嵌套类添加了一个不执行任何操作的内部静态Initialise()方法
  2. 向封闭类添加了一个静态构造函数,该构造函数调用嵌套类的Initialise()方法

这很好,但它留下了一点internal static void Initialise()方法形状的痈。

我的问题

有没有办法避免这种做法?我忍不住觉得我在上面链接的原始帖子中遗漏了一些东西。我误解了答案吗?

在我调用试图创建嵌套类实例的代码之前,有没有一种聪明的方法可以强制嵌套类的静态构造函数运行?

这种方法还有其他问题吗?

(我知道我可以为嵌套类编写一个公共接口,然后返回。这个问题不是要用这种方式解决的!(

样本代码

这是我的示例代码。试着运行它,它会打印"测试"。然后尝试注释掉标记为<--- If you comment this out, things won't work的行,然后再次运行它。

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

如何实现只能由其封闭类创建的密封的公共嵌套类

如果需要强制类构造函数在不引用类型的情况下运行,则可以使用此代码:

static Outer()
{
    System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (Inner).TypeHandle);
}

C#没有"friend"函数。一种务实的方法可能是简单地要求打电话的人证明他们是谁。实现这一点的一种方法可能是提供一个只有合法调用类才能知道的对象引用,即向外部类提供privateobject。假设您不分发该对象,唯一的解决方法就是非公共反射,如果您需要保护不受非公共反射的影响,那么这种情况就没有意义了,因为任何有权访问非公开反射的人都可以访问类似private构造函数的东西。所以类似于:

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

我还使用了静态构造函数来严格控制嵌套类的可访问性,最终得到了类似的过于复杂的代码。但我最终想出了一个简单明了的解决方案。基础是私有接口的显式实现。(无静态构造函数(

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

此解决方案还提供了编译时的安全性。虽然Outer.Inner.Factory可以在Outer之外实例化,但只能通过IInnerFactory接口调用方法CreateInner,这意味着只能在Outer中调用。以下几行不会在外部编译(有不同的错误(,即使在同一个程序集中也是如此:

    new Outer.Inner.Factory().CreateInner("");
    ((Outer.IInnerFactory)new Outer.Inner.Factory()).CreateInner("");