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

Posted

技术标签:

【中文标题】如何实现只能由其封闭类创建的密封公共嵌套类?【英文标题】:How to implement a sealed, public nested class that can only be created by its enclosing class? 【发布时间】:2013-05-02 13:26:22 【问题描述】:

目标

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

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

以前的工作

This post from a couple of years ago seems to be the answer。 (整个线程有很多关于我想要实现的信息。)

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

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

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

问题

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

我的解决方法

我已经解决了以下问题:

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

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

我的问题

有没有办法避免这种方式?我不禁认为我在上面链接的原始帖子中遗漏了一些东西。我是不是误会了答案?

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

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

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

示例代码

这是我的示例代码。尝试运行它,它会打印“Test”。然后尝试注释掉标记为<--- 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;
    

【问题讨论】:

如果您愿意使用internal Initialise() 方法,为什么不愿意使用internal 构造函数?这似乎是一个更简单的问题解决方案...... @MarcGravell 从调用者的角度来看,内部初始化方法不会做任何事情——它不会创建任何东西。而内部构造函数确实创造了一些东西;我认为这两件事完全不同。 @Matthew 那么让我问一下:为什么不愿意在这里使用internal @MarcGravell 实际上我很高兴将internal 用于Initialise 方法,但我想知道是否有一种方法可以使更简单的方法起作用 - 如果您阅读我链接的原始答案,它得到了很多赞成,这使它看起来像它的作品,所以我想知道我是否误解了它。我意识到这在某种程度上是一种学术练习,但我经常从学术练习中学到很多东西。 :) 我很好奇需要这种类设计的场景。你能详细说明一下吗? 【参考方案1】:

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

static Outer()

    System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (Inner).TypeHandle);

【讨论】:

确实有效。我只需要将 Outer 类的静态构造函数中的代码行 Inner.Initialise() 替换为您给我的代码行。然后我可以删除 Inner 类的 Initialise() 方法 Nice。 :) 另外,像这样显式调用它可以确保我不依赖于静态类初始化的未记录顺序的任何更改。顺便说一句,这并不完全是学术性的——我现在在实际代码中使用它。【参考方案2】:

C# 没有“朋友”功能。一种实用的方法可能是简单地要求调用者证明他们是谁。做到这一点的一种方法可能是提供一个只有 合法调用类 可以知道的对象引用,即向外部类提供一个 object,即 private。假设您不分发该对象,解决该问题的唯一方法是非公开反射,如果您需要防止非公开反射,那么这种情况就没有实际意义,因为任何可以访问非公开反射的人都可以已经可以访问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);
    

【讨论】:

谢谢。是的,我是一名前 C++ 程序员friend 关键字正是我用来解决这个特定问题的方式!这个 Token 想法虽然是一个很好的方法。它确实将其从编译时检测转移到运行时检测,这是一个小缺点。 @MatthewWatson 确实,它的好处是简单明了,即使对编译器也是如此 - 无需担心何时调用静态构造函数,这是一个很难准确回答的问题,并且这实际上在 .NET 版本之间发生了变化(这实际上可能会使旧答案 depend 依赖于静态构造函数 invalid 的副作用,例如 .NET 4.5)。至于运行时检测……是的,很棘手【参考方案3】:

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

    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 可以在外部实例化,但 CreateInner 方法只能通过 IInnerFactory 接口调用,这意味着只能在 Outer 中。 即使在同一个程序集中,以下行也不会在外部编译(出现不同的错误):

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

【讨论】:

以上是关于如何实现只能由其封闭类创建的密封公共嵌套类?的主要内容,如果未能解决你的问题,请参考以下文章

jqc#零基础学习之路抽象类和密封

kotlin学习总结——object关键字数据类密封类嵌套类和内部类

如何访问嵌套类的私有成员?

Kotlin中级- - - Kotlin类之数据类密封类内部类.md

Kotlin中级- - - Kotlin类之数据类密封类内部类.md

Kotlin中嵌套类数据类枚举类和密封类的详解