Как ограничить доступ к вложенному члену класса вмещающему классу?
Можно ли указать, что члены вложенного класса могут быть доступны посредством включающего класса, но не других классов?
Вот иллюстрация проблемы (конечно, мой реальный код немного сложнее...):
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
класс (несмотря на последнее требование в моем вопросе...)
7 ответов
Если ваш класс не слишком сложен, вы можете либо использовать интерфейс, который является публично видимым, и сделать фактический реализующий класс закрытым, либо вы можете сделать защищенный конструктор для 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);
}
}
В этом случае вы можете:
- Сделать конструктор внутренним - это останавливает тех, кто вне этой сборки, создает новые экземпляры или...
- Рефакторинг
JournalEntry
класс, чтобы использовать публичный интерфейс и сделать фактическийJournalEntry
класс частный или внутренний. Интерфейс может быть затем открыт для коллекций, а фактическая реализация скрыта.
Я упомянул в качестве действительного модификатора internal выше, однако, в зависимости от ваших требований, private может быть более подходящей альтернативой.
Изменить: Извините, я упомянул частного конструктора, но вы уже рассмотрели этот вопрос в своем вопросе. Приношу свои извинения за неправильное чтение!
Я бы сделал конструктор 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; }
}
// ...
}
Для общего вложенного класса =)
Я знаю, что это старый вопрос, и на него уже есть принятый ответ, тем не менее для тех пловцов 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);
}
}
У нас есть 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();
}
Выход:
Решение, предложенное Гризли, делает создание вложенного класса где-то еще немного сложным, но не невозможным, как Тим Полман написал, что кто-то еще может наследовать его и использовать наследующий класс 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
}
}
}
Это решение - не то, что я бы делал в обычный рабочий день, не потому, что оно приведет к проблемам в других местах, а потому, что оно нетрадиционно (я никогда не видел его раньше), поэтому оно может причинить боль другим разработчикам.