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

StackOverflow https://stackoverflow.com/questions/1664793

  •  12-09-2019
  •  | 
  •  

Вопрос

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

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

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 класс (несмотря на последнее требование в моем вопросе...)

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

Делать JournalEntry а частный вложенный тип.Любые открытые члены будут видны только включающему типу.

public class Journal
{
    private class JournalEntry
    {
    }
}

Если вам нужно сделать JournalEntry объекты, доступные другим классам, предоставляйте их через общедоступный интерфейс:

public interface IJournalEntry
{
}

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

    private class JournalEntry : IJournalEntry
    {
    }
}

Более простой подход – просто использовать 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);
    }
}

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

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

Выше я упомянул «внутренний» как допустимый модификатор, однако, в зависимости от ваших требований, «частный» может оказаться более подходящей альтернативой.

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

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

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

Я столкнулся с этим вопросом, поскольку мне нужно было реализовать ту же функцию, что и ОП.Для моего первого сценария этот и этот ответы работали нормально.Тем не менее мне также нужно было предоставить вложенный общий класс.Проблема в том, что вы не можете предоставить поле типа делегата (поле фабрики) с открытыми универсальными параметрами, не создавая собственный универсальный класс, но, очевидно, это не то, что мы хотим, поэтому вот мое решение для такого сценария:

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

У нас есть Бу как наш внутренний вложенный класс с частным конструктором, и мы поддерживаем в нашем родительском классе словарь с этими универсальными фабриками, используя преимущества динамичный.Итак, каждый раз TestMeЧувак вызывается, 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

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

    }
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top