Утилита для исследования типов в сборке



Мы поможем в написании ваших работ!


Мы поможем в написании ваших работ!



Мы поможем в написании ваших работ!


ЗНАЕТЕ ЛИ ВЫ?

Утилита для исследования типов в сборке



В состав .Net Framework SDK входит утилита для расшифровки метаданных – ildasm.exe. Хотя информация, предоставляемая этой утилитой, не всегда наглядна и очевидна (по причине сложности структуры метаданных), тем не менее, для проверки своего кода, работающего с метаданными, трудно найти лучшее подспорье.

Написать утилиту, во многом повторяющую функциональность ildasm.exe по исследованию типов, не составляет большого труда. Среди исходных текстов примеров, прилагающихся к этой статье, вы найдёте такую программу.

 

Гораздо большие возможности по исследованию и программированию типов предосталяет программа .Net Explorer, описанние которой вы найдёте на страницах этого журнала.

Исследование объекта

Итак, предположим, мы изучили класс досконально. Теперь мы знаем о нём всё. Как же воспользоваться этим знанием, если имеется готовый экземпляр класса? Как прочитать его поля, как получить значения его свойств, как вызывать его методы? Давайте для начала научимся читать поля и свойства объекта.

ПРИМЕЧАНИЕ - Эка невидаль, - скажете вы мне. - Прочитать поля и свойства объекта! Что ж тут такого? - Дело в том, что класс, экземпляр которого мы хотим создать и исследовать, не известен в момент написания программы. Более того, он не только не известен, он даже ещё и не написан! Его напишут после того, как программа будет скомпилирована!

Предположим, у вас имеется ссылка на объект неизвестного типа. В .Net это означает, что имеется ссылка на объект типа Object. Тип Object – это базовый тип CLR, и к нему можно привести любую ссылку (value-типы перед этим должны быть помещены в GC-хип, т.е. должна быть произведена операция boxing'а). У Object есть только базовый набор методов, через которые нельзя напрямую вызвать методы типа, приведенного к Object. Но с помощью Reflection можно динамически получить описание методов, полей или свойств объекта. А с помощью этого описания можно динамически к ним обращаться. Следующий пример считывает список полей и выводит их имена и значения:

public static void TraceObjectFields(Object obj){ foreach (FieldInfo mi in obj.GetType().GetFields()) Console.WriteLine(mi.FieldType.Name + " " + mi.Name + "= " + mi.GetValue(obj);}

А этот выводит список свойств объекта:

public static void TraceObjectProperties(Object obj){ foreach (PropertyInfo pi in obj.GetType().GetProperties()) Console.WriteLine(pi.PropertyType.Name + " " + pi.Name + " = " + pi.GetValue(obj, null);}

Трассировка полей объекта

В процессе отладки иногда возникает необходимость запротоколировать состояние некоторых важных для работы программы классов. Написание кода протоколирования полей – не самая интересная работа, да и времени она может отнять много. К тому же может оказаться, что самое интересное поле класса всё-таки осталось не выведенным, а ситуация была трудновоспроизводимой... В таком случае можно порекомендовать вывести просто все (!) поля класса, тем более, что механизм reflection позволяет делать это одной строчкой кода.

ПРИМЕЧАНИЕ - Для подобных задач предусмотрен метод ToString, - скажете вы: - Каждый класс обязан перекрыть этот метод и реализовать свой индивидуальный вывод в строку. Класс Trace использует ToString ровно для таких же задач. - Не спорю. Действительно, ToString позволяет точно отрегулировать, как и какие данные класса выводить. Но мне кажется, что если это делается только для отладочных целей, поддержка методов ToString в сотнях (а может и в тысячах) классов – дело очень хлопотное. Гораздо проще однажды написать функцию, подобную приведённой ниже, и использовать её при необходимости.

Трассировка полей объекта

static string ObjectFields(String pre, String name, Object obj){ String trace = ""; Type type = obj.GetType(); if (type.IsPrimitive) { trace += newLine + name + "= " + obj.ToString() + ", " + type.FullName + AddIf(pre); } else if (type.IsArray) { Type typeElement = type.GetElementType(); Array arr = (Array)obj; trace += newLine + name + ", Array of " + typeElement.FullName + ", Rank= " + arr.Rank + ", Length= " + arr.Length + AddIf(pre); int[] arrIdx = new int[arr.Rank]; for (int dim = 0; dim < arr.Rank; dim++) arrIdx[dim] = 0; trace += Indent(); for (int idx = arr.GetLowerBound(0); idx <= arr.GetUpperBound(0); idx++) { arrIdx[0] = idx; trace += ObjectFields("", name + "[" + idx + "]" , arr.GetValue(arrIdx)); } trace += Unindent(); } else if (type.IsClass || type.IsValueType) { if (objects[obj] != null) { trace = newLine + name + "= " + (string)objects[obj] + ", " + type.FullName ; } else { objects[obj] = name; trace = newLine + name + ", " + type.FullName + AddIf(pre); trace += Indent(); foreach (FieldInfo fi in type.GetFields ( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static )) { if (! NeedTrace(fi)) continue; string fieldName = name + "." + fi.Name; if (fi.IsStatic) { if (types[type.FullName+fi.Name] != null) { trace += newLine + fieldName + "= " + (string)types[type.FullName + fi.Name] + ", " + fi.FieldType.FullName + ", static"; break; } else { types[type.FullName + fi.Name] = fieldName; object field = fi.GetValue(obj); if (field == null) trace += newLine + fieldName + "= null" + ", " + fi.FieldType.FullName + ", static"; else trace += ObjectFields("static", fieldName , field); } } else // Not static { object field2 = fi.GetValue(obj); if (field2 == null) trace += newLine + fieldName + "= null" + ", " + fi.FieldType.FullName; else trace += ObjectFields("", fieldName, field2); } } trace += Unindent(); } } return trace;}...static Hashtable types = new Hashtable();static Hashtable objects = new Hashtable();

Вы легко сможете модифицировать код этой процедуры, если потребуется изменить формат вывода или выводить дополнительно свойства класса или другие его компоненты.

Обращу ваше внимание на то, что здесь используется вариант метода GetFields, позволяющий получать доступ как к открытым полям класса, так и к закрытым. Надо отметить, что доступ к закрытым членам возможен не всегда. Для чтения информации о типах необходимо, чтобы запрашивающий код имел по всей глубине стека вызовов привилегию ReflectionPermission с флагом TypeInformation.

ПРИМЕЧАНИЕ Еще одним бастионом безопасности кода в среде .Net является метод GetType() (тот самый, с рассмотрения которого начинался рассказ про метаданные). Дело в том, что метод GetType() не виртуальный, и поэтому злоумышленнику не удастся переопределить его, чтобы предоставить среде исполнения недостоверную информацию о типе и пробить тем самым брешь с системе защиты. GetType() реализуется самой средой исполнения, что гарантирует точную идентификацию типов и соблюдение правил безопасности.

Привилегии, разграничение доступа, система безопасности .Net – эти темы требуют отдельного, обстоятельного разговора, а пока воспользуемся тем, что в большинстве случаев метаданные доступны для чтения, и испытаем в деле процедуру протоколирования полей объекта.

Вот пример использования предложенного способа вывода полей класса.

class Data1 { public static int k = 11; int[,] b = {{1,0,0},{2,3,4},{5,0,0}}; string n = "reflection"; public Class1 c;} class Class1{ public Data1 d = new Data1(), d2; static void Main() { Class1 c = new Class1(); c.d.c = c; Console.WriteLine(Trace2.ObjectFields("c", c)); Console.ReadLine(); }}

Вот что должно вывестись на экран:

c, App1.Class1{ c.d2= null, App1.Data1 c.d, App1.Data1 { c.d.c= c, App1.Class1 c.d.k= 11, System.Int32, static c.d.b, Array of System.Int32, Rank= 2, Length= 9 { c.d.b[0]= 1, System.Int32 c.d.b[1]= 2, System.Int32 c.d.b[2]= 5, System.Int32 } c.d.n, System.String { c.d.n.m_arrayLength= 11, System.Int32 c.d.n.m_stringLength= 10, System.Int32 c.d.n.m_firstChar= r, System.Char c.d.n.Empty, System.String, static { c.d.n.Empty.m_arrayLength= 1, System.Int32 c.d.n.Empty.m_stringLength= 0, System.Int32 c.d.n.Empty.m_firstChar= , System.Char c.d.n.Empty.Empty= c.d2.n.Empty, System.String, static }... skiped } }}

Хочу обратить ваше внимание на имеющиеся здесь подводные камни. Посмотрим на поле типа string. Тип string имеет статическое поле Empty того же типа string. Поэтому, выводя поле Empty, вы снова обнаружите у него статическое поле Empty, выводя которое вы снова обнаружите статическое поле Empty... короче, получается бесконечная рекурсия. Выход простой – не выводить второй раз статические поля, а ссылаться на первый вывод этих полей.

Второй камушек связан с взаимными ссылками объектов друг на друга. В последнем примере класс Class1 имеет ссылку на класс Data1, а тот, в свою очередь, имеет ссылку на Class1. Если не предпринять специальных мер, то опять получится бесконечная рекурсия. Меры примерно такие же, как в предыдущем случае – запоминаем все выведенные объекты и не выводим их второй раз.

ПРИМЕЧАНИЕ Кстати, заглянув в исходники Rotor'а, можно увидеть, что стандартные процедуры сериализации реализованы в .Net способом аналогичным тому, каким здесь реализовано протоколирование. Про сериализацию в .Net советую прочитать серию статей Джефри Рихтера в недавних номерах MSDN Magazine.


Последнее изменение этой страницы: 2016-07-14; просмотров: 149; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 54.144.55.253 (0.006 с.)