73

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

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

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(尽管我的问题中有最后一个要求......)

4

7 回答 7

85

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

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

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

  public class JournalEntry
  {
    static JournalEntry()
    {
      _newJournalEntry = value => new JournalEntry(value);
    }
    private JournalEntry(object value)
    {
      ...
于 2009-11-03T06:29:26.577 回答
58

如果您的类不是太复杂,您可以使用公开可见的接口并将实际的实现类设为私有,或者您可以为该类创建一个受保护的构造函数并拥有一个派生自公共构造函数JornalEntry的私有类,该构造函数实际上是由您的.JornalEntryInstanceJornalEntryJournal

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

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

于 2009-11-03T02:18:20.500 回答
28

制作JournalEntry一个私有嵌套类型。任何公共成员将仅对封闭类型可见。

public class Journal
{
    private class JournalEntry
    {
    }
}

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

public interface IJournalEntry
{
}

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

    private class JournalEntry : IJournalEntry
    {
    }
}
于 2009-11-03T02:37:04.750 回答
13

一种更简单的方法是只使用构造函数,但通过提供只有合法调用者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);
    }
}
于 2013-05-02T13:46:09.263 回答
3

在这种情况下,您可以:

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

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

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

于 2009-11-03T02:07:34.063 回答
0

对于通用嵌套类 =)

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

我遇到了这个问题,因为我需要实现与 OP 相同的功能。对于我的第一个场景,这个这个答案工作得很好。尽管如此,我还需要公开一个嵌套的泛型类。问题是您不能在不使您自己的类泛型的情况下公开具有打开的泛型参数的委托类型字段(工厂字段),但显然这不是我们想要的,因此,这是我针对这种情况的解决方案:

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

输出是:

在此处输入图像描述

于 2018-02-23T16:05:00.413 回答
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
        }

    }
}

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

于 2018-07-16T05:08:49.490 回答