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

时间:2013-05-02 13:26:22

标签: c# constructor nested-class method-hiding

目标

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

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

以前的工作

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

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

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

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

问题

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

我的解决方法

我已经解决了以下问题:

  1. 向嵌套类添加了一个不执行任何操作的内部静态Initialise()方法。
  2. 向封闭类添加了一个静态构造函数,该构造函数调用嵌套类的Initialise()方法。
  3. 这样可以正常工作,但它会以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;
        }
    }
    

3 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:3)

如果您需要强制运行类构造函数而不引用该类型,则可以使用此代码:

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

答案 2 :(得分:1)

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

    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("");