KBAC
|
|
« : 30-12-2012 13:31 » |
|
Идея заключается в том, чтобы сделать класс, который, подобно суперпользоватею, мог бы делать все что захочет с любым другим классом, как бы суперкласс Например class OuterClass { private InnerClass member = new InnerClass(); }
class InnerClass { private int x = 9; } class SuperClass { public static void M_Some() { Type t = typeof(OuterClass); MemberInfo[] mi = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance); MemberInfo founded = null;
// Find our button foreach (var member in mi) { if (member.Name == "member") { founded = member; break; } }
// Чтобы изменить member.x надо иметь ссылку на member, но из MemberInfo нельзя достать сслыку
} } Допустим, мне надо изменить member.x объекта класса OuterClass из класса SuperClass. Средствами рефлексии не получится : чтобы изменить member.x надо иметь ссылку на member, но из MemberInfo нельзя достать ссыку на объект или вызвать подходящий метод для отраженного объекта. Если ошибаюсь, то будет очень здорово, когда меня кто-нибудь поправит. Если есть другие идеи, как можно представить такой механизм, то будет интересно почитать.
|
|
« Последнее редактирование: 30-12-2012 13:37 от KBAC »
|
Записан
|
У тебя все получится, главное -- верить и делать. Порадоваться, когда все плохо -- легче, чем ты думаешь. В действии счастье. Вовлекая людей важно быть увлеченным чужой жизнью.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #1 : 30-12-2012 13:39 » |
|
а friend там не канает ? а вообще - зачем это нужно ?
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #2 : 30-12-2012 14:17 » |
|
Что за ахинея?
Это попытка превратить C# из компилируемого языка в интерпретируемый? Пользуйтесь JScript.NET. Или это попытка замаскировать неспособность спроектировать нормальную архитектуру?
В скомпилированном коде ничего не меняется (как-то состав переменных, полей, классов и т.д.)!
Reflection нужен лишь для работы с динамически подключаемыми сборками, для .NET-обёрток над COM, выбора элементов кода по атрибутам (в основном для плагинов самой Visual Studio - дизайнеры, тесты) и т.п. вещей. Но никак не для изменений сборок!
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #3 : 30-12-2012 15:10 » |
|
KBAC, ты хочешь с помощью reflection для конкретного экземпляра OuterClass изменить значение member.x? Или то, о чём подумал Dimka? Если первое, то в этом не должно быть никаких трудностей..
|
|
|
Записан
|
|
|
|
KBAC
|
|
« Ответ #4 : 30-12-2012 16:34 » |
|
Это нужно для интерактивной командной строки в режиме выполнения программы. Допустим, надо проверить какой-то из путей выполнения программы, но ,чтобы его смоделировать, необходимо загрузить проект (довольно долгая операция). Алексей++, friend'ов, насколько я понял, нет в c#. Dimka, это попытка "сломать" систему прав доступа в готовом проекте на c#. Dmitry, первое; у меня не получилось. И, интерес, не в том, чтобы, блин, смоделировать конкретную ситуацию, вопрос в том, чтобы дать какому-либо классу God's Power ========= Даже если так: мы находим, что member это член OuterClass, запускаем OuterClass::HandleRequest public void HandleRequest() { Type t = this.GetType();
FieldInfo fi = t.GetField("member", BindingFlags.NonPublic | BindingFlags.Instance); if (fi != null) { FieldInfo fi = fi.FieldType.GetField("x", BindingFlags.NonPublic | BindingFlags.Instance); fi.SetValue(member, 11); // ================== тут надо указать имя объекта, у которого // будем менять соответсвующее поле. } } то все равно надо каким-то образом обратиться к объекту с именем, хранящемся в строке ("member"). Т.е. надо по строке получить имя объекта. public void HandleRequest(string request) Строка request содержит что-то вроде "outer.member.x = 11";
|
|
« Последнее редактирование: 30-12-2012 16:38 от KBAC »
|
Записан
|
У тебя все получится, главное -- верить и делать. Порадоваться, когда все плохо -- легче, чем ты думаешь. В действии счастье. Вовлекая людей важно быть увлеченным чужой жизнью.
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #5 : 30-12-2012 17:15 » |
|
Всё равно не понятно, чего ты хочешь добиться. В SetValue первым параметром передаётся объект (ссылка), для которого ты хочешь изменить значение поля. Где у тебя, собственно, сами объекты? Можно хранить их в хэш таблице, а ключи (имена) извлекать из строки запроса.
|
|
« Последнее редактирование: 30-12-2012 17:22 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #6 : 30-12-2012 17:37 » |
|
Dmitry, похоже, он хочет менять значение member у объектов, которые где-то внутри чужой сборки, и спрашивает, как ему через Reflection получить ссылку на нужный объект.
KBAC, во-первых, объектов класса может быть много и даже очень много, и они могут быть созданы по совершенно разным поводам, и нет в природе никакого оракула, который бы за тебя угадывал, какой из объектов нужен тебе для твоих действий. Во-вторых, нет в .NET никаких механизмов для получения всех объектов системы или всех объектов какого-либо класса.
Всего этого нет, потому что оно никому никогда не было и не будет нужно. В том числе и тебе - уверен. Если же речь идёт о каких-то взломах, то в .NET есть защиты от несанкционированного исполнения кода гораздо позабористей области видимости private.
И лучше бы тебе рассказать подробнее о решаемой задаче, чем пытаться искать читерские дыры в .NET.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #7 : 30-12-2012 17:42 » |
|
Dmitry, похоже, он хочет менять значение member у объектов, которые где-то внутри чужой сборки, и спрашивает, как ему через Reflection получить ссылку на нужный объект. Вот это и странно. Если хочется менять состояние каких-то объектов, то доступ к этим объектам имеется (ну раз известно, что они существуют и их можно как-то идентифицировать).
|
|
« Последнее редактирование: 30-12-2012 17:46 от Dmitry »
|
Записан
|
|
|
|
KBAC
|
|
« Ответ #8 : 30-12-2012 21:38 » |
|
Dmitry, про объект известно, т.к. я этот объект и создал в своем классе. И хочу изменить его в режиме run-time. Зачем - я уже писал. А доступа к этим объектам не будет по крайней мере из класа, который обрабатывает запрос (тот, кто должен передавать управление в HandleRequest). Но с доступом вроде как проблема решается, через вспомогательный метод HandleRequest и еще м.б. действительно хеш-таблицу, если она сумеет хранить ссылки на объекты.
|
|
« Последнее редактирование: 30-12-2012 21:45 от KBAC »
|
Записан
|
У тебя все получится, главное -- верить и делать. Порадоваться, когда все плохо -- легче, чем ты думаешь. В действии счастье. Вовлекая людей важно быть увлеченным чужой жизнью.
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #9 : 30-12-2012 22:11 » |
|
Да, под доступностью я имел в виду, что всё равно есть ссылка на некоторый объект. А является он целевым объектом или оболочкой, через которую нужно добираться до нужного объекта, не столь важно.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #10 : 31-12-2012 07:29 » |
|
про объект известно, т.к. я этот объект и создал в своем классе. Раз создал, значит есть ссылка. И в чём тогда проблема? Почему ты пытаешься эту ссылку получать противоестественым способом - через Reflections, вместо того, чтобы держать её полем своего собственного объекта? И причём тут хэш-таблица? А ключами у неё что является?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #11 : 31-12-2012 10:45 » |
|
И причём тут хэш-таблица? А ключами у неё что является?
ТС хочет обрабатывать объекты с помощью строки запроса, напр. "outer.member.x = 11". Я лишь предложил в таком случае хранить объекты в словаре с индексацией по имени переменной (или к.-н. идентификатору), которое выуживается из строки запроса. Ну и дальше строка парсится и обрабатывается с помощью reflections.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #12 : 31-12-2012 12:00 » |
|
Dmitry, а контроль типов? Или свалится - и ладно, пользователь сам виноват, что неправильную строку написал?
Не, это плохая затея. Тогда уж лучше PowerShell использовать или JScript.NET. Там хотя бы интерпретаторы уже готовые и отлаженные.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #13 : 31-12-2012 13:24 » |
|
Dimka, а что контроль типов? Тип любого объекта в рантайме всегда известен, как и то, есть ли у типа член с данным именем (и вся нужная информация о нём). Если нужно некоторому полю присвоить новое значение, достаточно узнать тип этого поля и получить из строки новое значение соответствующего типа. С точки зрения возможностей C# это кажется вполне неплохим вариантом (если не единственным). А другие технологии/языки нужно ещё и знать. А пока их не знаешь, хотя бы поверхностно, невозможно понять, нужны ли они тебе вообще Но кругозор, конечно, надо расширять.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #14 : 31-12-2012 14:18 » |
|
Собрал пример проекта с интерпретатором (не столько для автора темы, который вряд ли этим воспользуется, сколько для анналов истории). 1) Нужно определить набор доступных для интерпретатора классов, объектов и т.п. - того, чем может манипулировать тот, кто пишет код для интерпретатора. Договоримся весь этот библиотечный набор поместить в отдельную сборку и зададим для него пространство имён Library. Язык реализации библиотеки может быть произвольным, лишь бы в результате была получена нормальная .NET-сборка с классами, их полями, методами, свойствами и т.п. Для примера файл csl.cs // Сборка библиотеки для интерпретатора. Язык C#, но может быть произвольным. using System;
// Пространство имён, содержимое которого будет доступно в интерпретаторе. namespace Library {
// В частности будет доступен весь этот класс со всем его поведением. public class Test {
private int x;
public Test() { this.x = 0; }
public void Increment() { this.x += 1; }
public void Decrement() { this.x -= 1; }
public int Value { get { return this.x; } }
public bool Limit { get { return Math.Abs(this.x) >= 10; } }
}
} 2) Сделаем библиотечную сборку интерпретатора языка JScript.NET, воспользовавшись eval-конструкцией языка. Код напишем на JScript.NET. Договоримся собирать интерпретатор вместе с библиотекой, полученной в пункте 1. Итак код интерпретатора в файле jsi.js. // Сборка интерпретатора. Язык JScript.NET. Интерпретируемый язык также JScript.NET.
// Импорт пространств имён, .NET-классы которых будут доступны в интерпретаторе.
// Общесистемное пространство имён. import System; // Пространство имён нашей библиотеки. import Library;
package Interpreter {
class JScriptInterpreter {
// Доступное в интерпретаторе хранилище объектов между вызовами. private var cache;
public function JScriptInterpreter() { this.cache = new Object(); }
// Функция интерпретации. // Параметры: // expression - код для интерпретации; // Результат: описание ошибки. public function Interprete(expression: String): String { var Cache = this.cache; try { eval(expression); // Выполнение без ошибок. return ""; } catch(error: Error) { // Выполнение с ошибкой. return String(error); } }
}
} Здесь мы в коде заранее подключаем пространство имён библиотеки: во-первых, потому что в eval запрещён import, во-вторых, так надёжнее с точки зрения безопасности. Интерпретатор представляет собой обычный .NET-класс в соответствующем пространстве имён. Код в интерпретаторе можно выполнять либо разом, либо кусочками по мере его поступления/генерации. Для последнего случая в контекст интерпретатора добавлен объект Cache, в который можно помещать на сохранение какие-то значения или объекты между вызовами интерпретатора. 3) Наконец, нужно написать программу, которая использовала бы интерпретатор для исполнения строк с кодом на языке JScript.NET. Эта программа должна собираться в исполняемый файл со ссылками на сборку интерпретатора (который, в свою очередь, ссылается на библиотеку) и на сборку Microsoft.JScript. Программа, как и библиотека, может быть написана на любом языке, хоть том же C#. Файл программы csu.cs using System; using System.Text;
using Interpreter;
namespace User {
class Program {
static void Interprete(JScriptInterpreter interpreter, string code) { string error = interpreter.Interprete(code.ToString()); if(error != String.Empty) { Console.WriteLine("Error: {0}", error); } else { Console.WriteLine("OK"); } Console.WriteLine(); }
static void Main(string[] args) { JScriptInterpreter interpreter = new JScriptInterpreter(); StringBuilder code;
code = new StringBuilder(); code.AppendLine("Cache.test = new Test();"); code.AppendLine("Console.WriteLine('Test object is successfully created.');"); Program.Interprete(interpreter, code.ToString());
code = new StringBuilder(); code.AppendLine("Console.Write('Random test changing.');"); code.AppendLine("var test = Cache.test;"); code.AppendLine("var date = new Date();"); code.AppendLine("var cs = date.getSeconds();"); code.AppendLine("while(!test.Limit) {"); code.AppendLine(" var rs = Math.random() * 60;"); code.AppendLine(" if(rs > cs) {"); code.AppendLine(" test.Increment();"); code.AppendLine(" } else {"); code.AppendLine(" test.Decrement();"); code.AppendLine(" }"); code.AppendLine(" Console.Write('.');"); code.AppendLine("}"); code.AppendLine("Console.WriteLine();"); Program.Interprete(interpreter, code.ToString()); code = new StringBuilder(); code.AppendLine("Console.WriteLine('Current test value is {0}.', Cache.test.Value);"); code.AppendLine("Cache.test = null;"); Program.Interprete(interpreter, code.ToString()); }
}
} Тут мы трижды вызываем интерпретатор, и поэтому храним объект Test из нашей библиотеки в кэше интерпретатора. Программный код для манипуляции объектом Test может быть в общем-то произвольной сложности и размера. Но он не может быть разбит на модули и пространства имён. 4) Соберём всё это в исполняемый файл, для чего удобно создать командный файл сборки build.bat: @echo off
set cli=c:\windows\microsoft.net\framework\v2.0.50727 set jsc="%cli%\jsc.exe" set csc="%cli%\csc.exe"
%csc% /out:csl.dll /target:library csl.cs %jsc% /out:jsi.dll /target:library /reference:csl.dll jsi.js %csc% /out:csu.exe /target:exe /reference:jsi.dll /reference:Microsoft.JScript.dll csu.cs Как видно из этого кода, для сборки примера достаточно иметь .NET 2.0 - т.е. требования самые скромные. 5) Собираем >build Версия компилятора Microsoft (R) Visual C# 20058.00.50727.4927 для Microsoft (R) Windows (R) 2005 Framework версии2.0.50727 Авторские права (C) Microsoft Corporation 2001-2005. Все права защищены.
Microsoft (R) JScript Compiler, версия 8.00.50727 для Microsoft (R) .NET Framework версии 2.0.50727 (C) Корпорация Майкрософт, 1996-2005. Все права защищены.
Версия компилятора Microsoft (R) Visual C# 20058.00.50727.4927 для Microsoft (R) Windows (R) 2005 Framework версии2.0.50727 Авторские права (C) Microsoft Corporation 2001-2005. Все права защищены.
Всё хорошо, и 6) Запускаем: >csu Test object is successfully created. OK
Random test changing............................. OK
Current test value is -10. OK
Работает.
|
|
« Последнее редактирование: 31-12-2012 14:22 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #15 : 01-01-2013 14:00 » |
|
Для последнего случая в контекст интерпретатора добавлен объект Cache, в который можно помещать на сохранение какие-то значения или объекты между вызовами интерпретатора. По сути, это замыкание из выполняемого eval кода на локальную переменную Cache? Непонятно тогда, как сохраняется новый созданный объект между разными вызовами. Это, скорее, мысли вслух. Придёт время, разберусь. upd. а, понял. cache всегда один и тот же. Объекты между вызовами сохраняются в cache.test Кстати, подобный интерпретатор, только не ограниченный языком JScript.NET, можно реализовать с помощью CodeDom, существующего, кажись, с самой первой версии .NET. Скомпилированный в рантайме код (сборку) останется только подгрузить и вызвать нужный метод с помощью того же Reflections.
|
|
« Последнее редактирование: 01-01-2013 14:45 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #16 : 01-01-2013 17:38 » |
|
Объекты между вызовами сохраняются в cache.test Один объект - test, он один и тот же во всех вызовах, и он хранится в Cache. Cache.test - здесь test является произвольным идентификатором, он нигде не предзадан, возникает только в интерпретируемом коде по воле разработчика, формирующего исполняемый интерпретатором код, но благополучно сохраняется в Cache. В конце присваивается null для очистки памяти и освобождения (отвязывания) объекта test от контекста.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #17 : 01-01-2013 18:41 » |
|
Один объект - test, он один и тот же во всех вызовах, и он хранится в Cache. Я неправильно выразился. Под "cache всегда один и тот же" имел в виду, что поле cache с момента создания экземпляра класса JScriptInterpreter ссылается всё время на один и тот же объект, назовём его object. Ссылка на этот объект сохраняется в локальной переменной, на которую производится замыкание из интерпретируемого кода. Т.е. для каждого вызова интерпретатора создаётся новая переменная, поэтому сохранять состояния между вызовами интерпретатора можно только косвенно через неё. При первом вызове интерпретатора создаётся объект test (в данном примере он действительно один) и каким-то образом помещается в object с указанием идентификатора (напоминает dependency property). И при следующих вызовах интерпретатора объект test доступен через этот идентификатор.: Cache->object->test Так больше похоже на правду? Компилятор JScript.NET создаёт столько кода, сразу не разобраться, что там внутри происходит. Надо будет книжки почитать.
|
|
« Последнее редактирование: 01-01-2013 18:49 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #18 : 01-01-2013 19:14 » |
|
Dmitry, в JavaScript всякий объект - это ассоциативный массив, ключами которого являются идентификаторы (символы) или значения других типов (строки, числа, логические значения, функции, другие объекты и т.д.), значения же прозвольны. Это базовый примитив. У объекта есть стандартные поля constructor - ссылка на функцию-конструктор (играет роль типа объекта и используется оператором instanceof) и prototype - ссылка на объект-прототип, на который передаётся запрос ключа, если ключ не найден в самом объекте.
Замыкание контекста исполнения функции - по сути такой же объект, как всякий другой, локальные переменные и параметры функции хранятся в нём. Единственно, что на замыкание нельзя получить ссылку, чтобы использовать его как обычный объект. Однако при вызове функции с оператором new создаётся объект, оказывающийся прототипом замыкания. Ссылку на объект возвращает new, изнутри функции объект доступен через this, хотя (в силу связи по прототипу) this писать не обязательно, а поле constructor объекта связывается с исполняющейся функцией, сама функция может быть использована как конструктор объекта. При помощи стандартного метода call у функции (как объекта) можно заставить выполняться функцию в контексте заранее заданного объекта (т.е. замыканию функции назначить прототипом уже существующий объект).
Существует также объект Global, в котором находится всё то, что включено в среду языка, и с которым ассоциировано замыкание исполняемой программы (как содержимое неявной функции main).
Собственно, больше в JavaScript ничего и нет, и ассоциативного массива, нескольких простых типов и функций достаточно для построения программ любой сложности. Всё остальное - библиотеки.
|
|
« Последнее редактирование: 01-01-2013 19:18 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #19 : 01-01-2013 19:52 » |
|
Dimka, достаточно было ответа "Нет, это полный бред, всё не так" Просто меня совесть мучает спрашивать, можно сказать, основы. Всё-таки, для начала нужно хотя бы в книжку заглянуть. Благодарю за разъяснение! Завидую твоим ученикам. В хорошем смысле, естественно
|
|
|
Записан
|
|
|
|
KBAC
|
|
« Ответ #20 : 15-01-2013 14:02 » |
|
На вскидку (потому что не собирал такой проект). Dimka, с помощью твоего интепретатора я не смогу выполнить следующую инструкцию "test.x = 3;".
|
|
|
Записан
|
У тебя все получится, главное -- верить и делать. Порадоваться, когда все плохо -- легче, чем ты думаешь. В действии счастье. Вовлекая людей важно быть увлеченным чужой жизнью.
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #21 : 15-01-2013 14:33 » |
|
Не получится, конечно. Переданный в eval код выполняется в контексте ф-ии Interprete(), а оттуда поле x класса Test недоступно. Offtopic: Потихоньку изучаю F#. Радует декларативность, слегка смущают пробелы как элементы синтаксиса и немного загадочный вывод типов. Поставлю в угол.
|
|
« Последнее редактирование: 15-01-2013 14:50 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #22 : 15-01-2013 15:30 » |
|
KBAC, естественно, для test.X = 3 надо в Test добавить открытое свойство X или метод setX.
Ты объясни, зачем тебе лазать в закрытые члены классов? Например, у Рихтера где-нибудь написано, что надо лазать в закрытые члены? У Страуструпа? У Буча? наконец, у Вирта?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
KBAC
|
|
« Ответ #23 : 15-01-2013 16:23 » |
|
Dimka, кто много спрашивает, тому много врут Не сочти за грубость. Я отвечу для чего это все, когда моя идея образуется во что-то конкретное и детерминированное. Либо не отвечу вообще, если и говорить об этом уже не будет смысла.
|
|
|
Записан
|
У тебя все получится, главное -- верить и делать. Порадоваться, когда все плохо -- легче, чем ты думаешь. В действии счастье. Вовлекая людей важно быть увлеченным чужой жизнью.
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #24 : 15-01-2013 17:55 » |
|
KBAC, угу, либо это изобретение очередного велосипеда, связанного с сериализацией, либо хакерство - попытка через не то место работать с чужой сборкой (либо по собственной глупости, либо из-за чрезвычайно кривой архитектуры чужой сборки). Других задач, где бы мог бы требоваться доступ к закрытым полям, в природе нет.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
|