3

目标

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

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

之前的工作

几年前的这篇文章似乎是答案。(整个线程有很多关于我想要实现的目标的信息。)

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

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

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

问题

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

我的解决方法

我已经解决了以下问题:

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

3 回答 3

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);
    }
}
于 2013-05-02T13:41:11.040 回答
3

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

static Outer()
{
    System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof (Inner).TypeHandle);
}
于 2013-05-02T13:46:39.237 回答
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("");
于 2013-05-11T19:06:56.563 回答