如何限制对嵌套类成员的访问只在封闭类中可见?

76

是否可以指定嵌套类的成员只能被封闭类访问,而不能被其他类访问?

以下是问题的示例(当然我的实际代码会更加复杂...):

public class Journal
{
    public class JournalEntry
    {
        public JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    // ...
}

我希望防止客户端代码创建JournalEntry的实例,但是Journal必须能够创建这些实例。如果我将构造函数设置为public,则任何人都可以创建实例...但是如果我将其设置为private,则Journal将无法创建!

请注意,JournalEntry类必须是公共的,因为我想能够向客户端代码公开现有条目。

非常感谢大家提供的建议!最终,我选择了公共的IJournalEntry接口,并由一个私有的JournalEntry类实现(尽管与我问题中的最后一个要求不符...)


2
你可以将JournalEntry(object)构造函数设置为“internal”,这将防止其他程序集实例化日志条目,但同一程序集中的其他类仍然可以创建它们;但是,如果你是程序集的作者,这可能已经足够了。 - John K
4
是的,我考虑过这个,但我更希望即使在同一个程序集中也无法创建实例...不管怎样,谢谢! - Thomas Levesque
7个回答

93

实际上,有一种完整且简单的解决方案可以解决这个问题,而不需要修改客户端代码或创建接口。

对于大多数情况来说,这个解决方案比基于接口的解决方案更快,并且更容易编码。

public class Journal
{
  private static Func<object, JournalEntry> _newJournalEntry;

  public class JournalEntry
  {
    static JournalEntry()
    {
      _newJournalEntry = value => new JournalEntry(value);
    }
    private JournalEntry(object value)
    {
      ...

3
不错的独特方法...我得记住这个。谢谢! - Thomas Levesque
2
漂亮。被接受的答案有相同的基本思路,但它在一堆其他替代方案的底部,所以看这里! - aggieNick02
3
这很棒。谢谢。 - Ronnie Overby
11
我发现这种方法存在的问题是,因为.NET中的静态构造函数是惰性调用的,你可能会发现你的“_newJournalEntry”为空 - 但是“System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor”可以解决这个问题。 :) - Regent
4
我们会与您联系,不要拨打我们的电话。之后您可以给我们打电话。 - Kyle Delaney
显示剩余3条评论

60
如果你的类不是太复杂,你可以使用一个公开可见的接口,并将实际实现类设置为私有,或者你可以为JornalEntry类创建一个受保护的构造函数,然后创建一个私有类JornalEntryInstance,该类派生自JornalEntry,并具有由你的Journal实例化的公共构造函数。
public class Journal
{
    public class JournalEntry
    {
        protected JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    private class JournalEntryInstance: JournalEntry
    { 
        public JournalEntryInstance(object value): base(value)
        { }
    }
    JournalEntry CreateEntry(object value)
    {
        return new JournalEntryInstance(value);
    }
}

如果您的实际类太复杂,无法进行这两种操作,并且可以使构造函数不完全不可见,则可以将构造函数设置为内部,以便仅在程序集中可见。

如果那也行不通,您始终可以使构造函数私有并使用反射从日志类中调用它:

typeof(object).GetConstructor(new Type[] { }).Invoke(new Object[] { value });

现在我想了想,另一个可能性是在包含类中使用私有委托来从内部类设置。

public class Journal
{
    private static Func<object, JournalEntry> EntryFactory;
    public class JournalEntry
    {
        internal static void Initialize()
        {
            Journal.EntryFactory = CreateEntry;
        }
        private static JournalEntry CreateEntry(object value)
        {
            return new JournalEntry(value);
        }
        private JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    static Journal()
    {
        JournalEntry.Initialize();
    }
        
    static JournalEntry CreateEntry(object value)
    {
        return EntryFactory(value);
    }
}

这应该可以让你获得所需的可见性级别,而无需使用缓慢的反射或引入其他类/接口。


这个接口是一个好的解决方案,不知道为什么我没有想到它...谢谢! - Thomas Levesque
6
第一种方法有一个很大的缺陷:其他类也可以继承JournalEntry并调用构造函数。 - Tim Pohlmann
3
你现在可以将JournayEntry的构造函数设置为“private protected”,这样可以一定程度上防止继承者的攻击。 - mcintyre321
值(value)应该代表什么? - c0dezer019

32

JournalEntry作为私有嵌套类型。任何公共成员将只对包含类型可见。

public class Journal
{
    private class JournalEntry
    {
    }
}
如果你需要让其他类可以使用JournalEntry对象,可以通过公共接口来暴露它们:
public interface IJournalEntry
{
}

public class Journal
{
    public IEnumerable<IJournalEntry> Entries
    {
        get { ... }
    }

    private class JournalEntry : IJournalEntry
    {
    }
}

14
一个更简单的方法是只使用一个内部构造函数,但通过提供一个只有合法调用者才能知道的引用来证明他们的身份(我们不需要担心非公共反射,因为如果调用者可以访问非公共反射,则我们已经输了——他们可以直接访问私有构造函数);例如:
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 static Inner Create() {
        return new Inner(token);
    }
}

3
在这种情况下,你可以选择以下操作之一:
  1. 将构造函数设为internal - 这会阻止在该程序集之外创建新实例或者...
  2. 重构JournalEntry类以使用公共接口,并将实际的JournalEntry类设为private或internal。然后可以展示接口用于集合,同时隐藏实际实现。
我上面提到了internal作为有效的修饰符,但是根据你的要求,private可能是更适合的选择。 编辑:对不起,我提到了私有构造函数,但是你已经在问题中处理了这个点。我没有正确地阅读问题,对此表示歉意!

0

对于通用嵌套类 =)

我知道这是一个旧问题,并且它已经有了一个被接受的答案,但是对于那些可能有类似情况的谷歌搜索者,这个答案可能会提供一些帮助。

我遇到了这个问题,因为我需要实现与OP相同的功能。 对于我的第一个情况thisthis 的答案运作良好。 不过,我还需要公开一个嵌套的泛型类。 问题是,您不能公开具有打开的泛型参数的委托类型字段(工厂字段),而不使自己的类成为通用类,但显然这不是我们想要的,所以这是我针对这种情况的解决方案:

public class Foo
{
    private static readonly Dictionary<Type, dynamic> _factories = new Dictionary<Type, dynamic>();

    private static void AddFactory<T>(Func<Boo<T>> factory)
        => _factories[typeof(T)] = factory;

    public void TestMeDude<T>()
    {
        if (!_factories.TryGetValue(typeof(T), out var factory))
        {
            Console.WriteLine("Creating factory");
            RuntimeHelpers.RunClassConstructor(typeof(Boo<T>).TypeHandle);
            factory = _factories[typeof(T)];
        }
        else
        {
            Console.WriteLine("Factory previously created");
        }

        var boo = (Boo<T>)factory();
        boo.ToBeSure();
    }

    public class Boo<T>
    {
        static Boo() => AddFactory(() => new Boo<T>());

        private Boo() { }

        public void ToBeSure() => Console.WriteLine(typeof(T).Name);
    }
}

我们在父类中维护一个带有这些通用工厂的字典,利用动态性,将Boo作为我们的内部嵌套类,并具有私有构造函数。因此,每次调用TestMeDude时,Foo都会搜索是否已经创建了T的工厂,如果没有,则调用嵌套类的静态构造函数来创建它。
测试:
private static void Main()
{
    var foo = new Foo();

    foo.TestMeDude<string>();
    foo.TestMeDude<int>();
    foo.TestMeDude<Foo>();

    foo.TestMeDude<string>();

    Console.ReadLine();
}

输出结果为:

enter image description here


0

Grizzly提出的解决方案确实使得在其他地方创建嵌套类有点困难,但并非不可能,就像Tim Pohlmann所写的那样,仍然可以继承它并使用继承类ctor。

我正在利用嵌套类可以访问容器私有属性的事实,因此容器很礼貌地请求,而嵌套类则允许访问ctor。

 public class AllowedToEmailFunc
{
    private static Func<long, EmailPermit> CreatePermit;

    public class EmailPermit
    {
        public static void AllowIssuingPermits()
        {
            IssuegPermit = (long userId) =>
            {
                return new EmailPermit(userId);
            };
        }

        public readonly long UserId;

        private EmailPermit(long userId) 
        {
            UserId = userId;
        }
    }

    static AllowedToEmailFunc()
    {
        EmailPermit.AllowIssuingPermits();
    }

    public static bool AllowedToEmail(UserAndConf user)
    {
        var canEmail = true; /// code checking if we can email the user
        if (canEmail)
        {
            return IssuegPermit(user.UserId);
        }
        else
        {
            return null
        }

    }
}

这个解决方案不是我在工作中通常会使用的东西,不是因为它会导致其他地方出现问题,而是因为它不同寻常(我以前从未见过),所以可能会给其他开发人员带来困扰。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接