【问题标题】:How to restrict access to nested class member to enclosing class?如何将嵌套类成员的访问限制为封闭类?
【发布时间】:2010-12-12 11:54:12
【问题描述】:

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

这是一个问题的说明(当然我的实际代码有点复杂......):

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 必须能够创建它们。如果我将构造函数设为公开,任何人都可以创建实例……但如果我将其设为私有,Journal 将无法!

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

任何建议将不胜感激!


更新:感谢大家的意见,我最终选择了公共IJournalEntry 接口,由私有JournalEntry 类实现(尽管我的问题中的最后一个要求......)

【问题讨论】:

  • 您可以将 JournalEntry(object) 构造函数设为“内部”;这将阻止其他程序集实例化日记帐分录,但同一程序集中的其他类仍然可以创建它们;但是,如果您是程序集的作者,这可能就足够了。
  • 是的,我想到了,但我宁愿即使在同一个程序集中也无法创建实例......无论如何,谢谢!

标签: c# nested-class access-levels


【解决方案1】:

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

在大多数情况下,此解决方案实际上比基于接口的解决方案更快,并且更易于编码。

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

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

【讨论】:

  • 不错的原创方法...我必须记住那个。谢谢!
  • 漂亮。公认的答案具有相同的一般概念,但它位于许多其他替代方案的底部,所以请看这里!
  • 非常酷的技巧,让我免于维护单独界面的痛苦。不过感觉有点“hackish”——我不认为我会向初学者推荐这种设计模式;-)
  • 这会带来一些战利品。谢谢。
  • 我发现这种方法的问题是,因为 .NET 中的静态构造函数被延迟调用,您可能会发现自己的 _newJournalEntry 为空 - 但 System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor 可以节省时间。 :)
【解决方案2】:

如果您的类不是太复杂,您可以使用公开可见的接口并将实际的实现类设为私有,或者您可以为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);
    }
}

这应该为您提供所需的可见性级别,而无需诉诸缓慢的反射或引入额外的类/接口

【讨论】:

  • 界面是一个很好的解决方案,不知道为什么我没有想到...谢谢!
  • 第一种方式有个很大的缺点:其他类也可以继承JournalEntry,然后调用构造函数。
  • @TimPohlmann 您现在可以创建 JournayEntry ctor private protected,这应该可以为继承者提供一些防御。
【解决方案3】:

JournalEntry 设为私有嵌套类型。任何公共成员都将只对封闭类型可见。

public class Journal
{
    private class JournalEntry
    {
    }
}

如果您需要使 JournalEntry 对象对其他类可用,请通过公共接口公开它们:

public interface IJournalEntry
{
}

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

    private class JournalEntry : IJournalEntry
    {
    }
}

【讨论】:

    【解决方案4】:

    更简单的方法是只使用internal 构造函数,但通过提供只有合法调用者可以知道的引用来让调用者证明他们是谁(我们不需要关注非公共反射,因为如果调用者可以访问非公共反射,那么我们已经输了——他们可以直接访问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 static Inner Create() {
            return new Inner(token);
        }
    }
    

    【讨论】:

      【解决方案5】:

      在这种情况下,您可以:

      1. 将构造函数设为内部 - 这会阻止该程序集外部的那些创建新实例或...
      2. 重构JournalEntry 类以使用公共接口并使实际的JournalEntry 类私有或内部。然后可以为集合公开接口,而隐藏实际实现。

      我在上面提到了 internal 作为有效修饰符,但是根据您的要求,private 可能是更合适的选择。

      编辑:抱歉,我提到了私有构造函数,但您已经在问题中处理了这一点。我很抱歉没有正确阅读它!

      【讨论】:

        【解决方案6】:

        对于通用嵌套 class=)

        我知道这是一个老问题,它已经有一个公认的答案,但是对于那些可能有类似情况的谷歌游泳者来说,这个答案可能会提供一些帮助。

        我遇到了这个问题,因为我需要实现与 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 作为具有私有构造函数的内部嵌套类,并且我们在父类中维护一个字典,其中这些通用工厂利用 dynamic。因此,每次调用 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();
        }
        

        输出是:

        【讨论】:

          【解决方案7】:

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

          这个解决方案不是我在日常工作中会做的事情,不是因为它会导致其他地方出现问题,而是因为它非常规(我以前从未见过)所以它可能会给其他开发人员带来痛苦。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-07-16
            • 1970-01-01
            • 1970-01-01
            • 2023-03-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-11-13
            相关资源
            最近更新 更多