Как получить доступ к закрытым членам вложенного класса?
Предыстория: Я включил (родительский) класс E с вложенным классом N с несколькими экземплярами N в E. В закрытом (родительском) классе я делаю некоторые вычисления, и я устанавливаю значения для каждого экземпляра вложенного класса. Что-то вроде этого:
n1.field1 = ...;
n1.field2 = ...;
n1.field3 = ...;
n2.field1 = ...;
...
Это один большой метод eval (в родительском классе). Мое намерение - поскольку все вычисления находятся в родительском классе (они не могут быть выполнены для каждого вложенного экземпляра, потому что это сделает код более сложным) - сделайте сеттеры доступными только для родительского класса и геттеров.
И теперь возникает проблема:
- когда я делаю сеттеры частными, родительский класс не может их использовать
- когда я делаю их общедоступными, каждый может изменить значения
- и у С# нет концепции друзей
- Я не могу передавать значения в конструкторе, потому что используется ленивый механизм оценки (поэтому экземпляры должны создаваться при их обращении - я создаю все объекты, а расчет запускается по запросу)
Я застрял - как это сделать (ограничьте доступ до родительского класса, не более, не менее)?
Я подозреваю, что сначала получу ответный вопрос: "но почему вы не разделили оценку на каждое поле" - так я отвечу на этот пример: как вы вычисляете минимальное и максимальное значение коллекции? Быстро? Ответ - за один проход. Вот почему у меня есть одна функция eval, которая выполняет вычисления и устанавливает сразу все поля.
Ответы
Ответ 1
Если вы можете поместить родительский и дочерний классы в другую сборку, вы можете использовать internal
для сеттеров. В общем, как это происходит в дикой природе.
ИЗМЕНИТЬ
Ответ Томаса Левеска дал мне идею:
class Program
{
static void Main(string[] args)
{
E myE = new E();
Console.WriteLine("E.N1.Field1 = " + myE.N1.Field1);
Console.WriteLine("E.N2.Field1 = " + myE.N2.Field1);
}
public interface IN
{
int Field1 { get; }
}
public class E
{
private N _n1 = new N();
private N _n2 = new N();
public E()
{
_n1.Field1 = 42;
_n2.Field1 = 23;
}
public IN N1
{
get { return _n1; }
}
public IN N2
{
get { return _n2; }
}
private class N : IN
{
private int _field1;
public int Field1
{
get { return _field1; }
set { _field1 = value; }
}
}
}
}
В зависимости от того, как вам нужно выставить дочерний класс N
, это может сработать.
Ответ 2
Вы можете объявить внутри E
частный интерфейс IN
, явно реализованный N
. Этот интерфейс предоставит членам N
доступ только для E
:
public class E
{
public void Foo()
{
IN n = new N();
n.Field1 = 42;
}
public class N : IN
{
private int _field1;
int IN.Field1
{
get { return _field1; }
set { _field1 = value; }
}
}
private interface IN
{
int Field1 { get; set; }
}
}
Ответ 3
Другая альтернатива - оставить членов, которые вы хотите быть частным публичным , если ваш (вложенный) класс является приватным. Если поля частного класса являются общедоступными, то его доступ к открытому классу.
public class E
{
public void Foo()
{
IN n = new N();
n.field1 = 42;
}
class N : IN
{
public int _field1;
}
}
Теперь N
доступен только для E
, поэтому n._field1
является открытым только для E
, и вы в безопасности.
Ответ 4
Это старый вопрос, но здесь идет возможное решение, которое не использует интерфейсы.
У вас может быть статическая функция во внутреннем классе, которая устанавливает делегаты во внешнем классе, например:
public class Outer {
private delegate void _operateDlg(Inner inner, bool value);
private static _operateDlg _validate;
static Outer() {
Inner.Init();
}
public void Set(Inner inner, bool value) {
_validate(inner, value);
}
public class Inner {
public bool IsValid {get; private set; }
public static void Init() {
Outer._validate += delegate(Inner i, bool value) {
i.IsValid = value;
};
}
}
}
Вы можете поместить все типы делегатов во внешний класс, которые вы назначаете с помощью метода Inner.Init(), например методы, возвращающие экземпляр класса Inner через частный конструктор или getters/seters определенного поля,
Если вы не возражаете иметь дополнительную статическую функцию Init() в своем внутреннем классе, тогда это не должно меняться. Но если вы не хотите, чтобы метод Init() был видимым, вы можете использовать отражение для его вызова:
using System.Reflection;
public class Outer {
private delegate void _operateDlg(Inner inner, bool value);
private static _operateDlg _validate;
static Outer() {
typeof(Inner).GetMethod("Init",
BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
}
public void Set(Inner inner, bool value) {
_validate(inner, value);
}
public class Inner {
public bool IsValid {get; private set; }
private static void Init() {
Outer._validate = delegate(Inner i, bool value) {
i.IsValid = value;
};
}
}
}
Я знаю, что можно использовать Reflection для обхода ограничений частного доступа в любом случае, но использовать его просто для вызова одного метода Init(), который затем назначает соответствующих делегатов, является гораздо более чистым и более универсальным решением, на мой взгляд. Альтернативой будет вызов рефлексии для каждого отдельного делегата, который вы, возможно, захотите создать, и даже тогда могут быть ограничения (например, невозможность создания делегатов для конструкторов).
Вышеупомянутое решение поддерживает не только конструкторы wrapping, но и использует Reflection один раз в течение жизни программы, поэтому не должно быть заметного штрафа за производительность, кроме того, что вы используете делегатов для достижения того, что должно были разрешены в качестве прямого доступа в первую очередь. Я не знаю, почему С# не поддерживает это, и я не могу придумать, почему это не так.
Ответ 5
сделать поля "защищенными внутренними"
Если вложенные классы являются частными, вы можете использовать obly "internal" для этих полей.