Как ограничить доступ к вложенному члену класса включению класса?
Можно ли указать, что доступ к элементам вложенного класса может быть вызван окружающим классом, но не другими классами?
Вот иллюстрация проблемы (конечно, мой фактический код немного сложнее...):
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
(несмотря на последнее требование в моем вопросе...)
Ответы
Ответ 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
}
}
}
Это решение не то, что я делал бы в обычный день работы, а не потому, что это приведет к проблемам в других местах, а потому, что оно нетрадиционное (я никогда не видел его раньше), поэтому это может привести к больным другим разработчикам.