Подтвердить что ты не робот

Как ограничить доступ к вложенному члену класса включению класса?

Можно ли указать, что доступ к элементам вложенного класса может быть вызван окружающим классом, но не другими классами?

Вот иллюстрация проблемы (конечно, мой фактический код немного сложнее...):

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 должен быть общедоступным, потому что я хочу, чтобы он мог показывать существующие записи в код клиента.

Любое предложение будет оценено!


UPDATE: Спасибо всем за ваш вклад, я в конце концов пошел на общедоступный интерфейс IJournalEntry, реализованный частным классом JournalEntry (несмотря на последнее требование в моем вопросе...)

4b9b3361

Ответ 1

Если ваш класс не слишком сложный, вы можете использовать интерфейс, который является общедоступным, и сделать фактический класс реализации приватным, или вы можете сделать защищенный конструктор для класса 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);
    }
}

Это должно дать вам желаемые уровни видимости без необходимости прибегать к медленному отражению или вводить дополнительные классы/интерфейсы.

Ответ 2

На самом деле есть полное и простое решение этой проблемы, которое не включает в себя модификацию клиентского кода или создание интерфейса.

Это решение на самом деле быстрее, чем решение на основе интерфейса, в большинстве случаев, и легче кодировать.

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

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

Ответ 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

В этом случае вы можете либо:

  • Сделать внутренний конструктор - это останавливает тех, кто находится вне этой сборки, создает новые экземпляры или...
  • Реорганизовать класс JournalEntry для использования открытого интерфейса и сделать фактический JournalEntry класс закрытым или внутренним. Затем интерфейс может быть открыт для коллекций, пока фактическая реализация не скрыта.

Я упомянул внутреннее как действительный модификатор выше, однако, в зависимости от ваших требований, частные могут быть лучшей альтернативой.

Изменить: Извините, я упомянул о частном конструкторе, но вы уже рассматривали этот вопрос в своем вопросе. Приношу свои извинения за неправильное чтение!

Ответ 6

Я бы сделал конструктор JournalEntry внутренним:

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

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

    // ...
}

Ответ 7

Для общего вложенного класса =)

Я знаю, что это старый вопрос, и у него уже есть принятый ответ, тем не менее для тех плагинов google, которые могут иметь похожий сценарий для моего ответа, может оказать некоторую помощь.

Я столкнулся с этим вопросом, потому что мне нужно было реализовать ту же функцию, что и 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 как наш внутренний вложенный класс с частным конструктором, и мы предлагаем в нашем родительском классе словарь с этими родовыми фабриками, используя динамические возможности. Таким образом, каждый раз, когда вызывается 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

Ответ 8

Решение, предложенное Гризли, немного затрудняет создание вложенного класса в другом месте, но не невозможно, например, Тим Полманн написал, что кто-то все еще может наследовать его и использовать наследующий класс 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
        }

    }
}

Это решение не то, что я делал бы в обычный день работы, а не потому, что это приведет к проблемам в других местах, а потому, что оно нетрадиционное (я никогда не видел его раньше), поэтому это может привести к больным другим разработчикам.