Pull to refresh

Comments 27

Вы переусложнили GetSecretUsingExpressionTrees, в этом случае достаточно такого:
public static int GetSecretUsingExpressionTrees(this SecretKeeper keeper)
{
    ParameterExpression keeperArg = Expression.Parameter(typeof(SecretKeeper), "keeper"); // SecretKeeper keeper argument
    Expression secretAccessor = Expression.Field(keeperArg, "_secret"); // keeper._secret
    var lambda = Expression.Lambda<Func<SecretKeeper, int>>(secretAccessor, keeperArg);
    var func = lambda.Compile(); // Получается функция return result = keeper._secret;

    return func(keeper);
}
Спасибо, такой вариант действительно проще и быстрее. Внес его в статью.
Валидный путь раскрывать приватные поля без «хаков» это под-класс https://gist.github.com/deniszykov/556ddc0a1d335c96fb58b808ac66c894
Вы правы, nested классы видят поля внешних классов. При этом они довольно схожи с protected полями: также нужно создать дополнительный класс для доступа к секрету, также требуется изменить основной класс. Полагаю, область применения также совпадает.
Быстродействие, по идее, тоже одинаковое, но тут надо смотреть. Завтра замерю, выложу результаты.
В любом случае, спасибо за 5 способ.
Неожиданно. Вложенные классы оказались шустрее наследников.

Inheritor: 5.4560ns
Nested: 4.6916ns
Хм, 3 метод отказыватся работать на .NET 4.5. Не хочет выдавать он приватные поля. Тогда как для public работает как и задумывалось.
Только что проверил на 4, 4.5, 4.6 — все ок. VS 2015, AnyCPU, Debug.
Вам не кажется, что задача немного надуманная: реализовывать доступ к приватному полю класса, если есть возможность модифицировать этот самый класс? В этом случае, всё решается public свойством/методом. В случае, когда такого доступа нет и поле действительно private, то единственный способ — это рефлексия в той или иной вариации (примеры 2 и 3 в вашей статье).
Бывают варианты.
У одной крупной мировой корпорации в коде для одного продукта была либа версии 5.0. В ней класс c двумя приватными полями. В версии 7.0 одно поле было публичным, а доступ к другому осуществляли через рефлексию. Возможность модификации есть, одного поля public нет, причины неизвестны.
Лично я однажды не мог изменять написанный мною же класс: он к тому времени функционировал уже три месяца, был полон г**нокода, и полностью подпадал под резолюцию «работает — не трогай».
1) Имхо, для тестов, способ 3 — самый годный. Плюс лямбда-геттер надо бы закэшировать.
Имхо, вложенный класс для раскрытия состояния (конкретно для тестовых случаев) — способ неправильный и нагружает класс лишним знанием о том, что кто-то должен будет к приватному члену обратиться. Лучше поправить упавшие тесты при рефакторинге приватного члена, на который тесты заточены, чем вносить лишнюю ответственность в класс.

2) Тестировать внутреннее состояние не совсем правильно, ибо по канонам ООП, класс должен обеспечивать корректную работу с внешней средой через публичный API. А как он это делает внутри — его личное дело.
1. Согласен. Единственно, про protected механику знает почти любой джун, а с nested классами и рефлексией все немного хуже, что в будущем может аукнуться при поддержке. Разве что, вынести акцессор в отдельный метод GetPrivate(object obj, string fieldName)…
А какой модификатор доступа в данном случае будет у GetPrivate()?
В контексте
конкретно для тестовых случаев

public static class PrivateMembersHelper // Создаем в тестовом проекте
{
  public static object GetPrivate(this object obj, string memberName){...}
}
Вопрос новичка:
А что означает эта строка:
protected int SecretForInheritors => _secret;
Точнее, оператор => в ней?
Это ведь лямбда-оператор?
Сокращенный вариант readonly свойства:
protected int SecretForInheritors { get { return _secret; } }

Так же можно и функции обьявлять:
public int Mult(int x, int m) => x * m;
А можно ссылку (или примерное название) на статью, упомянутую в первом абзаце? Я так понимаю, это фишка C#? Думается мне, что совместно с методами расширения довольно интересно можно расстрелять себе ноги.
Ноги расстрелять нельзя, т.к. методы расширения имеют доступ только к публичным членам
Не могу найти статью. Даже мой комментарий к ней пропал. Возможно, автор скрыл в черновики.
По поводу методов расширения — для того, чтобы метод видел эту переменную, он должен быть членом SecretKeeper (напрямую или в nested классе), так что для расстрельной ситуации придется постараться.
public void DoSomething(Example example)
  {
    this.JustInt = example.JustInt; // Вполне валидная строка, некоторых удивляет
  }

Наверное, такое может удивить только программистов, не представляющих, что такое копирующий конструктор.
Вообще говоря, в C# нет копирующего конструктора для классов, да и со структурами все не особо просто.
Опеределили. В шарпике копия объекта создается через реализацию интерфейса ICloneable и вызова метода Clone() соответственно.

Для красоты рефлексию можно завернуть в DynamicObject:


List<int> realList = new List<int>();
dynamic exposedList = ExposedObject.From(realList);
// Read a private field - prints 0
Console.WriteLine(exposedList._size);

Подробности и ссылка на библиотеку.


Таким же образом можно завернуть и вариант с expression trees.

А можно вопрос? Для чего всё это? Скрытые поля ведь не просто так сделаны скрытыми. Если б разработчик хотел, чтоб к полю был доступ он бы сделал это либо через публичное свойство, или (если он старовер) через публичные get()-set() функции.
Я вижу 2 варианта:
1) Желание прострелить себе ногу модифицируя осознано скрытые разработчиком поля объектов, т.е. явно нарушить архитектуру приложения;
2) Изобрести костыль к кривому приложению, который, опять-же может оказаться выстрелом в ногу и сделать приложение ещё кривее.
Последняя задача — кастомный сериализатор, который работает со скрытыми полями. В принципе, можно было обойти ту проблему иначе, но за счет пары дополнительных факторов было решено пилить свой сериализатор, в том числе с доступом к приватным полям.

По вашей градации — пункт 2. Разве что, багов после «костыля» стало меньше, и ноги пока целы.
Sign up to leave a comment.

Articles