Введение в программирование на C# 2.0

Атрибуты, сборки, рефлексия

Показывать лекцию целиком

Рефлексия (Reflection) – предоставление выполняемому коду информации о нем самом.

Рефлексия представляет собой процесс анализа типов (структуры типов) в ходе выполнения приложения (сборки). В .NET рефлексия реализуется свойствами и методами класса System.Type и классов пространства имен System.Reflection.

Пространство имен System.Reflection

Пространство имен System.Reflection содержит классы и интерфейсы, которые позволяют организовать просмотр загруженных в сборку типов, методов, полей (данных-членов) и обеспечивают динамические механизмы реализации типов и вызова методов.

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

System.Reflection определяет типы для организации позднего связывания и динамической загрузки типов.

Неполный перечень классов представлен ниже:

ТипНазначение
Assembly Методы для загрузки, описания и выполнения разнообразных операций над сборкой
AssemblyName Информация о сборке (идентификатор, версия, язык реализации)
EventInfo Информация о событиях
FieldInfo Информация о полях
MemberInfo Абстрактный базовый класс, определяющий общие члены для EventInfo, FieldInfo, MethodInfo, PropertyInfo
MethodInfo Информация о методе
Module Позволяет обратиться к модулю в многофайловой сборке
ParameterInfo Информация о параметре
PropertyInfo Информация о свойстве

Класс System.Type

Класс System.Type содержит методы, позволяющие получать информацию о типах приложения. Является основой для реализации функциональности пространства имен System.Reflection и средством для получения доступа к метаданным.

Использование членов класса Type позволяет получить информацию о:

  • типе ( GetType(string) );
  • конструкторах ( GetConstructors() );
  • методах ( GetMethods() );
  • данных-членах ( GetFields() );
  • свойствах ( GetProperties() );
  • событиях, объявленных в классе ( GetEvents() );
  • модуле;
  • сборке, в которой реализуется данный класс.
  • Объект — представитель класса Type уникален. Две ссылки на объекты — представители класса Type оказываются эквивалентными, если только объекты были созданы в результате обращения к одному и тому же типу.

    Объект — представитель класса Type может представить любой из следующих типов:

  • классы;
  • типы-значения;
  • массивы;
  • интерфейсы;
  • указатели;
  • перечисления.
  • Ссылка на объект — представитель класса Type, ассоциированная с некоторым типом, может быть получена одним из следующих способов.

  • В результате вызова метода
    Type Object.GetType()

    (метода — члена класса Object ), который возвращает ссылку на объект — представитель типа Type, представляющий информацию о типе. Вызов производится от имени объекта — представителя данного типа. Вызов становится возможен в связи с тем, что любой класс наследует тип Object.

  • В результате вызова статического метода — члена класса Type
    public static Type Type.GetType(string)

    параметром является строка со значением имени типа. Возвращает объект — представитель класса Type, с информацией о типе, специфицированном параметром метода.

  • От имени объекта — представителя класса Assembly — от имени объекта-сборки (самоописываемого, многократно используемого, версифицируемого БЛОКА (фрагмента) CLR-приложения) вызываются методы
    Type[] Assembly.GetTypes()
    Type Assembly.GetType(string)
    
    // Получаем ссылку на сборку, содержащую объявление типа MyType,
    // затем – массив объектов - представителей класса Type.
    Type[] types = (Assembly.GetAssembly(typeof(MyType))).GetTypes();
    // Без комментариев.
    Type tt = (Assembly.GetAssembly(typeof(MyType))).GetType("MyType");

    Здесь используется операция typeof. Операндом этой унарной операции является обозначение класса (в буквальном смысле – имя класса).

    class MyType
    {
    :::::
    }
    
    :::::
    Type t = typeof(MyType);
  • От имени объекта – представителя класса Module (модуль – portable executable файл с расширением .dll или .exe, состоящий из одного и более классов и интерфейсов):
    Type[] Module.GetTypes()
    Type Module.GetType(string)
    Type[] Module.FindTypes(TypeFilter filter, object filterCriteria)

    где TypeFilter – класс-делегат.

  • В результате выполнения операции typeof(), которая применяется для построения объекта-представителя класса System.Type. Выражение, построенное на основе операции typeof, имеет следующий вид:
    typeof(type)

    Операнд выражения – тип, для которого может быть построен объект – представитель класса System.Type.

    Пример применения операции:

    using System;
    using System.Reflection;
    
    public class MyClass 
    {
    public int intI;
    public void MyMeth() 
    {
     }
    
    public static void Main() 
    {
    Type t = typeof(MyClass);
    // Альтернативная эквивалентная конструкция
    // MyClass t1 = new MyClass();
    // Type t = t1.GetType();
    
    MethodInfo[] x = t.GetMethods();
    foreach (MethodInfo m in x) 
    {
    Console.WriteLine(m.ToString());
     }
    
    Console.WriteLine();
    MemberInfo[] x2 = t.GetMembers();
    foreach (MemberInfo m in x2) 
    {
    Console.WriteLine(m.ToString());
     }
     }
    }
  • Реализация отражения. Type, InvokeMember, BindingFlags

    Сначала – определения.

    Раннее (статическое) связывание – деятельность, выполняемая на стадии компиляции и позволяющая:

  • обнаружить и идентифицировать объявленные в приложении типы;
  • выявить и идентифицировать члены класса;
  • подготовить при выполнении приложения вызов методов и свойств, доступ к значениям полей – членов класса.
  • Позднее (динамическое) связывание – деятельность, выполняемая непосредственно при выполнении приложения и позволяющая:

  • обнаружить и идентифицировать объявленные в приложении типы;
  • выявить и идентифицировать члены класса;
  • обеспечить в ходе выполнения приложения вызов методов и свойств, доступ к значениям полей – членов класса.
  • При этом вызов методов и свойств при выполнении приложения обеспечивается методом InvokeMember. Этот метод выполняет достаточно сложную работу и поэтому нуждается в изощренной системе управления, для реализации которой применяется перечисление BindingFlags. В рамках этого перечисления определяются значения флажков, которые управляют процессом динамического связывания в ходе реализации отражения.

    Перечисление также применяется для управления методом GetMethod.

    Список элементов перечисления прилагается.

    Имя элементаОписание
    CreateInstance Определяет, что отражение должно создавать экземпляр заданного типа. Вызывает конструктор, соответствующий указанным аргументам. Предоставленное имя пользователя не обрабатывается. Если тип поиска не указан, будут использованы флаги (Instance | Public). Инициализатор типа вызвать нельзя
    DeclaredOnly Определяет, что должны рассматриваться только члены, объявленные на уровне переданной иерархии типов. Наследуемые члены не учитываются
    Default Определяет отсутствие флагов связывания
    ExactBinding Определяет, что типы представленных аргументов должны точно соответствовать типам соответствующих формальных параметров. Если вызывающий оператор передает непустой объект Binder, отражение создает исключение, так как при этом вызывающий оператор предоставляет реализации BindToXXX, которые выберут соответствующий метод
    ChangeType Отражение моделирует правила доступа для системы общих типов. Например, если вызывающий оператор находится в той же сборке, ему не нужны специальные разрешения относительно внутренних членов. В противном случае вызывающему оператору потребуется ReflectionPermission. Этот метод применяется при поиске защищенных, закрытых и т. п. членов.

    Главный принцип заключается в том, что ChangeType должен выполнять только расширяющее преобразование, которое никогда не теряет данных. Примером расширяющего преобразования является преобразование 32-разрядного целого числа со знаком в 64-разрядное целое число со знаком. Этим оно отличается от сужающего преобразования, при котором возможна потеря данных. Примером сужающего преобразования является преобразование 64-разрядного целого числа со знаком в 32-разрядное целое число со знаком.

    Связыватель по умолчанию не обрабатывает этот флаг, но пользовательские связыватели используют семантику этого флага

    FlattenHierarchy Определяет, что должны быть возвращены статические члены вверх по иерархии. Статические члены — это поля, методы, события и свойства. Вложенные типы не возвращаются
    GetField Определяет, что должно возвращаться значение указанного поля
    GetProperty Определяет, что должно возвращаться значение указанного свойства
    IgnoreCase Определяет, что при связывании не должен учитываться регистр имени члена
    IgnoreReturn Используется при COM-взаимодействии для определения того, что возвращаемое значение члена может быть проигнорировано
    Instance Определяет, что в поиск должны быть включены члены экземпляра
    InvokeMethod Определяет, что метод должен быть вызван. Метод не может быть ни конструктором, ни инициализатором типа
    NonPublic Определяет, что в поиск должны быть включены члены экземпляра, не являющиеся открытыми ( public )
    OptionalParamBinding Возвращает набор членов, у которых количество параметров соответствует количеству переданных аргументов. Флаг связывания используется для методов с параметрами, у которых есть значения методов, и для функций с переменным количеством аргументов (varargs). Этот флаг должен использоваться только с Type.InvokeMember.

    Параметры со значениями по умолчанию используются только в тех вызовах, где опущены конечные аргументы. Они должны быть последними аргументами

    Public Определяет, что открытые ( public ) члены должны быть включены в поиск
    PutDispProperty Определяет, что для COM-объекта должен быть вызван член PROPPUT. PROPPUT задает устанавливающую свойство функцию, использующую значение. Следует использовать PutDispProperty, если для свойства заданы и PROPPUT, и PROP-PUTREF и нужно различать вызываемые методы
    PutRefDispProperty Определяет, что для COM-объекта должен быть вызван член PROPPUTREF. PROPPUTREF использует устанавливающую свойство функцию, использующую ссылку, вместо значения. Следует использовать PutRefDispProperty, если для свойства заданы и PROPPUT, и PROPPUTREF и нужно различать вызываемые методы
    SetField Определяет, что должно устанавливаться значение указанного поля
    SetProperty Определяет, что должно устанавливаться значение указанного свойства. Для COM-свойств задание этого флага связывания эквивалентно заданию PutDispProperty и PutRefDispProperty
    Static Определяет, что в поиск должны быть включены статические члены
    SuppressChangeType Не реализован

    Далее демонстрируется применение класса Type, в частности варианты использования метода – члена класса Type InvokeMember, который обеспечивает выполнения методов и свойств класса.

    using System;
    using System.Reflection;
    // В классе объявлены поле myField, конструктор, метод String ToString(), свойство.
    class MyType 
    {
    int myField;
    public MyType(ref int x)
    {
    x *= 5;
    }
    
    public override String ToString()
    {
    Console.WriteLine("This is: public override String ToString() method!");
    return myField.ToString();
    }
    
    // Свойство MyProp нашего класса обладает одной замечательной особенностью:
    // значение поля myField объекта - представителя класса MyType не может 
    // быть меньше нуля. Если это ограничение нарушается – возбуждается 
    // исключение. 
    public int MyProp 
    {
    get 
    {
    return myField;
    }
    set 
    { 
    if (value < 1) 
    throw new ArgumentOutOfRangeException("value", value, "value must be > 1");
    myField = value;
    }
    }
    }
    
    class MyApp 
    {
    static void Main() 
    {
    
    // Создали объект - представитель класса Type
    // на основе объявления класса MyType.
    Type t = typeof(MyType);	
    
    // А это одномерный массив объектов, содержащий ОДИН элемент.
    // В этом массиве будут передаваться параметры конструктору.
    Object[] args = new Object[] {8};
    Console.WriteLine("The value of x before the constructor is called is {0}.", args[0]);
    
    // Вот таким образом в рамках технологии отражения производится 
    // обращение к конструктору. Наш объект адресуется по ссылке obj.
    Object obj = t.InvokeMember(
    null,
    //____________________________
    BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.CreateInstance, // Вот распоряжение о создании объекта... 
    //____________________________
    null,
    null,
    args // А так организуется передача параметров в конструктор. 
    );
    
    Console.WriteLine("Type: " + obj.GetType().ToString());
    Console.WriteLine("The value of x after the constructor returns is {0}.", args[0]);
    // Изменение (запись и чтение) значения поля myField только что 
    // созданного объекта - представителя класса MyType.
    // Как известно, этот объект адресуется по
    // ссылке obj. Мы сами его по этой ссылке расположили! 
    t.InvokeMember(
    "myField", // Будем менять значение поля myField...  
    //______________________________
    BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.SetField, // Вот инструкция по изменению значения поля.
    //_______________________________ 
    null,
    obj, // Вот указание на то, ГДЕ располагается объект... 
    new Object[] {5} // А вот и само значение. Оно упаковывается в массив объектов.
    );
    int v = (Int32) t.InvokeMember(
    "myField", 
    //______________________________
    BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.GetField, // А сейчас мы извлекаем значение поля myField. 
    //______________________________
    null,
    obj, // "Работаем" все с тем же объектом. Значение поля myField
        // присваивается переменной v.
    null
    );
    
    // Вот распечатали это значение.
    Console.WriteLine("myField: " + v);
    // "От имени" объекта будем вызывать нестатический метод.
    String s = (String) t.InvokeMember(
    "ToString", // Имя переопределенного виртуального метода.
    //______________________________
    BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.InvokeMethod, // Сомнений нет! Вызываем метод!
    //______________________________
    null,
    obj,   // От имени нашего объекта вызываем метод без параметров.
     null
     );
    
     // Теперь обращаемся к свойству.
     Console.WriteLine("ToString: " + s);
     // Изменение значения свойства. Пытаемся присвоить недозволенное 
     // значение. И посмотрим, что будет...
     // В конце концов, мы предусмотрели перехватчик исключения.
     try 
     {
     t.InvokeMember(
     "MyProp", // Работаем со свойством.
     //______________________________
     BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.SetProperty, // Установить значение свойства.
    //______________________________
    null,
    obj,
    new Object[] {0} // Пробуем через обращение к свойству
    // установить недозволенное значение.
    );
    } 
    catch (TargetInvocationException e) 
    {
    // Фильтруем исключения... Реагируем только на исключения типа
    // ArgumentOutOfRangeException. Все остальные "проваливаем дальше". 
    if (e.InnerException.GetType() !=typeof (ArgumentOutOfRangeException)) throw;
    // А вот как реагируем на ArgumentOutOfRangeException.
    // Вот так скромненько уведомляем о попытке присвоения запрещенного 
    // значения.
    Console.WriteLine("Exception! Catch the property set.");
    }
    
    t.InvokeMember(
    "MyProp", 
    //______________________________
    BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.SetProperty, // Установить значение свойства. 
    //______________________________
    null,
    obj,
    new Object[] {2} // Вновь присваиваемое значение. Теперь ПРАВИЛЬНОЕ.
    );
    
    v = (int) t.InvokeMember(
    "MyProp", 
    BindingFlags.DeclaredOnly | 
    BindingFlags.Public |
    BindingFlags.NonPublic | 
    BindingFlags.Instance |
    BindingFlags.GetProperty, // Прочитать значение свойства.
    null,
     obj,
     null
     );
    
     Console.WriteLine("MyProp: " + v);
     }
     }

    Ну вот. Создавали объект, изменяли значение его поля (данного-члена), вызывали его (нестатический) метод, обращались к свойству (подсовывали ему некорректные значения). И при этом НИ РАЗУ НЕ НАЗЫВАЛИ ВЕЩИ СВОИМИ ИМЕНАМИ! В сущности, ЭТО И ЕСТЬ ОТРАЖЕНИЕ.

    Атрибуты

    Атрибут – средство добавления ДЕКЛАРАТИВНОЙ информации к элементам программного кода. Назначение атрибутов – внесение всевозможных не предусмотренных обычным ходом выполнения приложения изменений:

  • описание взаимодействия между модулями;
  • дополнительная информация, используемая при работе с данными (управление сериализацией);
  • отладка;
  • и многое другое.
  • Эта декларативная информация составляет часть метаданных кода. Она может быть использована при помощи механизмов отражения.

    Структура атрибута регламентирована. Атрибут – это класс. Общий предок всех атрибутов – класс System.Attribute.

    Информация, закодированная с использованием атрибутов, становится доступной в процессе ОТРАЖЕНИЯ (рефлексии типов).

    Атрибуты типизированы.

    .NET способна прочитать информацию в атрибутах и использовать ее в соответствии с предопределенными правилами или замыслами разработчика. Различаются:

  • предопределенные атрибуты. В .NET реализовано множество атрибутов с предопределенными значениями:

    DllImport – для загрузки .dll-файлов;

    Serializable – означает возможность сериализации свойств объекта – представителя класса;

    NonSerialized – обозначает данные-члены класса как несериализуемые. Карандаши (средство графического представления информации, элемент GDI+) не сериализуются;

  • производные (пользовательские) атрибуты могут определяться и использоваться в соответствии с замыслами разработчика. Возможно создание собственных (пользовательских) атрибутов. Главные условия:
  • соблюдение синтаксиса;
  • соблюдение принципа наследования.
  • В основе пользовательских атрибутов – все та же система типов с наследованием от базового класса System.Attribute.

    И неспроста! В конце концов, информация, содержащаяся в атрибутах, предназначается для CLR, и она в случае необходимости должна суметь разобраться в этой информации. Пользователи или другие инструментальные средства должны уметь кодировать и декодировать эту информацию.

    Добавлять атрибуты можно к:

  • сборкам;
  • классам;
  • элементам класса;
  • структурам;
  • элементам структур;
  • параметрам;
  • возвращаемым значениям.
  • Ниже приводится описание членов класса Attribute.

    Открытые свойства
    TypeId При реализации в производном классе получает уникальный идентификатор для этого атрибута Attribute
    Открытые методы
    Equals Переопределен (см. Object.Equals )
    GetCustomAttribute Перегружен. Извлекает пользовательский атрибут указанного типа, который применен к заданному члену класса
    GetCustomAttributes Перегружен. Извлекает массив пользовательских атрибутов указанного типа, которые применены к заданному члену класса
    GetHashCode Переопределен. Возвращает хэш-код для этого экземпляра
    GetType (унаследовано от Object ) Возвращает Type текущего экземпляра
    IsDefaultAttribute При переопределении в производном классе возвращает значение, показывающее, является ли значение этого производного экземпляра значением по умолчанию для производного класса
    IsDefined Перегружен. Определяет, применены ли какие-либо пользовательские атрибуты заданного типа к указанному члену класса
    Match При переопределении в производном классе возвращает значение, указывающее, является ли этот экземпляр эквивалентным заданному объекту
    ToString (унаследовано от Object ) Возвращает String, который представляет текущий Object
    Защищенные конструкторы
    Attribute -конструктор Инициализирует новый экземпляр класса Attribute
    Защищенные методы
    Finalize (унаследовано от Object ) Переопределен. Позволяет объекту Object попытаться освободить ресурсы и выполнить другие завершающие операции, перед тем как объект Object будет уничтожен в процессе сборки мусора.

    В C# для функций финализации используется синтаксис деструктора

    MemberwiseClone (унаследовано от Object ) Создает неполную копию текущего Object

    Следующий пример является демонстрацией объявления и применения производных атрибутов:

    using System;
     using System.Reflection;
    
    namespace CustomAttrCS 
     {
     // Перечисление of animals.
     // Start at 1 (0 = uninitialized).
     public enum Animal 
     {
     // Pets.
     Dog = 1,
     Cat,
     Bool,
     }
    
     // Перечисление of colors.
     // Start at 1 (0 = uninitialized).
     public enum Color 
     {
     // Colors.
     Red = 1,
     Brown,
     White,
     }
    
    
     // Класс пользовательских атрибутов.
     public class AnimalTypeAttribute : Attribute 
     {//==============================================================
     // Данное - член типа "перечисление". 
    protected Animal thePet;
    protected string WhoIs(Animal keyPet)
     { 
    string retStr = "";
     switch (keyPet)
     {
     case Animal.Dog: retStr = "This is the Dog!"; break; 
    case Animal.Cat: retStr = "This is the Cat!"; break; 
    case Animal.Bull: retStr = "This is the Bool!"; break; 
    default: retStr = "Unknown animal!"; break;
     }
    
    return retStr;
     }
    
     // Конструктор вызывается при установке атрибута.
     public AnimalTypeAttribute(Animal pet) 
     {
     thePet = pet;
     Console.WriteLine("{0}", WhoIs(pet));
     }
    
     // Свойство, демонстрирующее значение атрибута.
     public Animal Pet 
     {
     get
     {
     return thePet;
     }
     set
     {
     thePet = value;
     }
     }
     }//==============================================================
    
     // Еще один класс пользовательских атрибутов.
     public class ColorTypeAttribute : Attribute 
     {//==============================================================
     // Данное - член типа "перечисление". 
    protected Color theColor;
    
     // Конструктор вызывается при установке атрибута.
     public ColorTypeAttribute(Color keyColor) 
     {
     theColor = keyColor;
     }
    
     // Свойство, демонстрирующее значение атрибута.
     public Color ColorIs 
     {
     get
     {
     return theColor;
     }
     set
     {
     theColor = ColorIs;
     }
     }
     }//==============================================================
    
    
     // A test class where each method has its own pet.
     class AnimalTypeTestClass 
     {//==============================================================
     // Содержит объявления трех методов, каждый из которых
     // предваряется соответствующим ПОЛЬЗОВАТЕЛЬСКИМ атрибутом.
     // У метода может быть не более одного атрибута данного типа.
     [AnimalType(Animal.Dog)]
     [ColorType(Color.Brown)]
     public void DogMethod() 
     {
     Console.WriteLine("This is DogMethod()...");
     }
    
     [AnimalType(Animal.Cat)]
     public void CatMethod() 	
     {
     Console.WriteLine("This is CatMethod()...");
     }
    
    
     [AnimalType(Animal.Bool)]
     [ColorType(Color.Red)]
     public void BoolMethod(int n, string voice)
     {
     int i;
     Console.WriteLine("This is BoolMethod!");
     	
     if (n > 0) for (i = 0; i < n; i++)
      {
     Console.WriteLine(voice);
      }
     }
     }//==============================================================
    
     class DemoClass 
     {//==============================================================
     static void Main(string[] args) 
     {
     int invokeFlag;
     int i;
    
     // И вот ради чего вся эта накрутка производилась...
     // Объект класса AnimalTypeTestClass под именем testClass
     // представляет собой КОЛЛЕКЦИЮ методов, каждый из которых
     // снабжен соответствующим ранее определенным пользовательским
     // СТАНДАРТНЫМ атрибутом. У класса атрибута AnimalTypeAttribute есть все,
     // что положено иметь классу, включая конструктор.
     AnimalTypeTestClass testClass = new AnimalTypeTestClass();
    
     // Так вот создали соответствующий объект - представитель класса...
     // Объект - представитель класса сам может служить источником 
     // информации о собственном классе. Информация о классе представляется 
     // методом GetType() в виде
     // объекта - представителя класса Type. Информационная капсула! 
    Type type = testClass.GetType();
    
     // Из этой капсулы можно извлечь множество всякой "полезной" информации...
     // Например, можно получить коллекцию (массив) элементов типа MethodInfo
     // (описателей методов), которая содержит список описателей методов, 
     // объявленных в данном классе. В список будет включена информация 
     // о ВСЕХ методах класса: о тех, которые были определены явным 
     // образом, и о тех, которые были унаследованы
     // от базовых классов. И по этому списку описателей методов мы 
     // пройдем победным маршем ("Ha-Ha-Ha") оператором foreach.
     i = 0;
    
    foreach(MethodInfo mInfo 
    in
    type.GetMethods()) 
     {
     invokeFlag = 0;
     Console.WriteLine("#####{0}#####{1}#####", i, mInfo.Name);
     // И у каждого из методов мы спросим относительно множества атрибутов,
     // которыми метод был снабжен при объявлении класса.
     foreach (Attribute attr
    in 
    Attribute.GetCustomAttributes(mInfo)) 
     {
     Console.WriteLine("~~~~~~~~~~");
     // Check for the AnimalType attribute.
     if (attr.GetType() == typeof(AnimalTypeAttribute))
     {
     Console.WriteLine("Method {0} has a pet {1} attribute.", 
    mInfo.Name,
     ((AnimalTypeAttribute)attr).Pet);
     // Посмотрели значение атрибута – и если это Animal.Bool – подняли флажок.
     if (((AnimalTypeAttribute)attr).Pet.CompareTo(Animal.Bool) == 0)
                                                     invokeFlag++;
     }
    
    if (attr.GetType() == typeof(ColorTypeAttribute))
     {
     Console.WriteLine("Method {0} has a color {1} attribute.", 
     mInfo.Name,
     ((ColorTypeAttribute)attr).ColorIs);
    
     // Посмотрели значение атрибута – и если это Color.Red –
     // подняли флажок второй раз.
     if (((ColorTypeAttribute)attr).ColorIs.CompareTo(Color.Red) == 0)
     invokeFlag++;
     }
    
     // И если случилось счастливое совпадение значений атрибутов метода
     // (Красный Бычок), то метод выполняется. 
     // Метод Invoke в варианте с двумя параметрами:
     // объект - представитель исследуемого класса
     // (в данном случае AnimalTypeTestClass), и массив объектов-параметров. 
    if (invokeFlag == 2)
     {
     object[] param = {5,"Mmmuuu–uu–uu!!! Mmm..."};
     mInfo.Invoke(new AnimalTypeTestClass(),param);
     }
    
    Console.WriteLine("~~~~~~~~~~");
     }
    
    Console.WriteLine("#####{0}#####", i);
     i++;
     }
     }
     }//==============================================================
     }

    Эта программа демонстрирует одну замечательную особенность синтаксиса объявления атрибутов. При их применении можно использовать сокращенные имена ранее объявленных классов атрибутов.

    В программе объявлено два класса атрибутов – наследников класса Attribute:

    public class AnimalTypeAttribute : Attribute...
    public class ColorTypeAttribute : Attribute...

    При объявлении соответствующих экземпляров атрибутов последняя часть имени класса атрибута ( ...Attribute ) была опущена.

    Вместо

    [AnimalTypeAttribute(Animal.Dog)]

    используется имя

    [AnimalType(Animal.Dog)],

    вместо

    [ColorTypeAttribute(Color.Red)]

    используется

    [ColorType(Color.Red)]

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

    Сборка. Класс Assembly

    Класс Assembly определяет Сборку – основной строительный блок Common Language Runtime приложения. Как строительный блок CLR, сборка обладает следующими основными свойствами:

  • возможностью многократного применения;
  • versionable (версифицированностью);
  • самоописываемостью.
  • Эти понятия являются ключевыми для решения проблемы отслеживания версии и для упрощения развертывания приложений во время выполнения.

    Сборки обеспечивают инфраструктуру, которая позволяет во время выполнения полностью "понимать" структуру и содержимое приложения и контролировать версии и зависимости элементов выполняемого приложения.

    Сборки бывают:

  • частными (private). Представляют наборы типов, которые могут быть использованы только теми приложениями, где они включены в состав. Располагаются в файлах с расширениями .dll (.exe) и .pdb (program debug Database). Для того чтобы использовать в приложении частную сборку, ее надо ВКЛЮЧИТЬ в приложение, то есть разместить в каталоге приложения (application directory) или в одном из его подкаталогов;
  • общего доступа (shared). Также набор типов и ресурсов внутри модулей (модуль – двоичный файл сборки). Предназначены для использования НЕОГРАНИЧЕННЫМ количеством приложений на клиентском компьютере. Эти сборки устанавливаются не в каталог приложения, а в специальный каталог, называемый Глобальным Кэшем Сборок (Global Assembly Cache – GAC). Кэш на платформе Windows XP усилиями специальной утилиты принимает вид каталога (путь ...\WINDOWS\assembly). Таким образом, в .NET ВСЕ совместно используемые сборки собираются в одном месте. Имя ("общее имя" или "строгое имя") сборки общего доступа строится с использованием информации о версии сборки.
  • Загружаемая сборка строится как БИБЛИТОТЕКА КЛАССОВ (файл с расширением .dll), либо как выполняемый модуль (файл с расширением .exe).

    Если это файл с расширением .dll, то в среде Visual Studio ее использование поддерживается специальными средствами среды. Это "полуавтоматическая" загрузка частной сборки в Reference приложения (Add Reference...). Сборки, располагаемые в .exe-файлах, особой поддержкой для включения сборки в состав приложения не пользуются.

    Для анализа сборки применяется утилита IlDasm.exe, которую можно подключить к непосредственно вызываемому из среды разработки Visual Studio списку утилит.

    Ниже представлены члены класса сборки.

    Открытые свойства
    CodeBase Возвращает местонахождение сборки, указанное первоначально, например в объекте AssemblyName
    EntryPoint Возвращает точку входа для этой сборки
    EscapedCodeBase Возвращает URI, предоставляющий базовый код, включая escape-знаки
    Evidence Возвращает свидетельство для этой сборки
    FullName Возвращает отображаемое имя сборки
    GlobalAssemblyCache Возвращает значение, показывающее, была ли сборка загружена из глобального кэша сборок
    ImageRuntimeVersion Возвращает версию общеязыковой среды выполнения (CLR), сохраненной в файле, который содержит манифест
    Location Возвращает местонахождение в формате базового кода загруженного файла, содержащего манифест, если для него не было теневого копирования
    Открытые методы
    CreateInstance Перегружен. Находит тип в этой сборке и создает его экземпляр, используя абстрактный метод
    CreateQualifiedName Статический. Создает тип, задаваемый отображаемым именем его сборки
    Equals (унаследовано от Object ) Перегружен. Определяет, равны ли два экземпляра Object
    GetAssembly Статический. Возвращает сборку, в которой определяется заданный класс
    GetCallingAssembly Статический. Возвращает Assembly метода, который вызывает текущий метод выполнения
    GetCustomAttributes Перегружен. Возвращает пользовательские атрибуты для этой сборки
    GetEntryAssembly Статический. Возвращает процесс, исполняемый в домене приложения по умолчанию. В других доменах приложений это первый исполняемый процесс, который был выполнен AppDomain.ExecuteAssembly
    GetExecutingAssembly Статический. Возвращает Assembly, из которой исполняется текущий код
    GetExportedTypes Возвращает экспортируемые типы, определенные в этой сборке
    GetFile Возвращает объект FileStream для указанного файла из таблицы файлов манифеста данной сборки
    GetFiles Перегружен. Возвращает файлы в таблице файлов манифеста сборки.
    GetHashCode (унаследовано от Object ) Служит хэш-функцией для конкретного типа, пригоден для использования в алгоритмах хэширования и структурах данных, например в хэш-таблице
    GetLoadedModules Перегружен. Возвращает все загруженные модули, являющиеся частью этой сборки
    GetManifestResourceInfo Возвращает информацию о способе сохранения данного ресурса
    GetManifestResourceNames Возвращает имена всех ресурсов в этой сборке
    GetManifestResourceStream Перегружен. Загружает указанный ресурс манифеста из сборки
    GetModule Возвращает указанный модуль этой сборки
    GetModules Перегружен. Возвращает все модули, являющиеся частью этой сборки
    GetName Перегружен. Возвращает AssemblyName для этой сборки
    GetObjectData Возвращает сведения сериализации со всеми данными, необходимыми для повторного создания этой сборки
    GetReferencedAssemblies Возвращает объекты AssemblyName для всех сборок, на которые ссылается данная сборка
    GetSatelliteAssembly Перегружен. Возвращает сопутствующую сборку
    GetType Перегружен. Возвращает объект Type, предоставляющий указанный тип
    GetTypes Возвращает типы, определенные в этой сборке
    IsDefined Показывает, определен ли пользовательский атрибут, заданный указанным значением Type
    Load Статический. Перегружен. Загружает сборку
    LoadFile Статический. Перегружен. Загружает содержимое файла сборки
    LoadFrom Статический. Перегружен. Загружает сборку
    LoadModule Перегружен. Загружает внутренний модуль этой сборки
    LoadWithPartialName Статический. Перегружен. Загружает сборку из папки приложения или из глобального кэша сборок, используя частичное имя
    ToString Переопределен. Возвращает полное имя сборки, также называемое отображаемым именем
    Открытые события
    ModuleResolve Возникает, когда загрузчик классов общеязыковой среды выполнения не может обработать ссылку на внутренний модуль сборки, используя обычные средства
    Защищенные методы
    Finalize (унаследовано от Object ) Переопределен. Позволяет объекту Object попытаться освободить ресурсы и выполнить другие завершающие операции, перед тем как объект Object будет уничтожен в процессе сборки мусора.

    В языках C# и C++ для функций финализации используется синтаксис деструктора

    MemberwiseClone (унаследовано от Object ) Создает неполную копию текущего Object

    Класс сборки в действии

    Исследование свойств и областей применения класса Assembly начинаем с создания тестовой однофайловой сборки AssemblyForStart в рамках проекта Class Library.

    Первая сборка Operators00.exe:

    using System;
     namespace Operators00
     {
     public class xPoint
     {
     float x, y;
     xPoint()
     {
     x = 0.0F;
     y = 0.0F;
     }
    
    public xPoint(float xKey, float yKey):this()
     {
     x = xKey;
     y = yKey;
     }
    
    public static bool operator true(xPoint xp)
     {
     if (xp.x != 0.0F  xp.y != 0.0F) return true;
     else return false;
     }
    
    public static bool operator false(xPoint xp)
     {
     if (xp.x == 0.0F || xp.y == 0.0F) return false;
     else return true;
     }
    
    
    public static xPoint operator | (xPoint key1, xPoint key2)
     {
     if (key1) return key1;
     if (key2) return key2;
     return new xPoint();	
     }
    
    public static xPoint operator  (xPoint key1, xPoint key2)
     {
     if (key1  key2) return new xPoint(1.0F, 1.0F);
     return new xPoint();	
     }
    
    
    public void Hello()
     {
     Console.WriteLine("Hello! Point {0},{1} is here!",this.x,this.y);	
     }
     }
    
    class Class1
     {
    
     // The main entry Point for the application.
    
     static void Main() // У точки входа пустой список параметров.
     // Я пока не сумел ей передать через метод Invoke массива строк. 
     {
     xPoint xp0 = new xPoint(1.0F, 1.0F);	 
    xPoint xp1 = new xPoint(1.0F, 1.0F);
    
    if (xp0 || xp1) Console.WriteLine("xp0 || xp1 is true!");
     else Console.WriteLine("xp0 || xp1 is false!");
     }
     }
     }

    Вторая сборка AssemblyForStart.dll. В примере она так и не запускалась. Используется только для тестирования стандартных средств загрузки сборок – библиотек классов:

    using System;
     namespace AssemblyForStart
     {
     // Class1 : первая компонента сборки AssemblyForStart. 
    
    public class Class1
     {
     public Class1()
     {
     Console.WriteLine("This is constructor Class1()");
     }
    
    public void fC1()
     {
     Console.WriteLine("This is fC1()");
     }
     } 
     }

    А вот полигон AssemblyStarter.exe. В примере демонстрируется техника ПОЗДНЕГО СВЯЗЫВАНИЯ. Именно поэтому код, который выполняется после загрузки сборки, не содержит в явном виде информации об используемых в приложении типах:

    using System;
     using System.Reflection;
     using System.IO;
    
    namespace AssemblyStarter
     {
    
     // Приложение обеспечивает запуск сборки.
    
    class Class1
     {
    
    static void Main(string[] args)
     {
     // Сборка может быть вызвана непосредственно по имени
     // (строковый литерал с дружественным именем сборки).
     // Ничего особенного. Просто имя без всяких там расширений.
     // Главная проблема заключается в том, что сборки должны 
     // предварительно включаться в раздел References (Ссылки).
     // Кроме того, информация о загружаемой сборке может быть представлена
     // в виде объекта - представителя класса AssemblyName, ссылка 
     // на который также может быть передана в качестве аргумента методу 
     // Assembly.Load().
     // Вот здесь как раз и происходит формирование этого самого объекта. 
     AssemblyName asmName = new AssemblyName();
     asmName.Name = "AssemblyForStart";
     // Версию подсмотрели в манифесте сборки с помощью IlDasm.exe.
     Version v = new Version("1.0.1790.25124");
     // Можно было бы для пущей крутизны кода поле Version 
     // проинициализировать непосредственно (дело хозяйское):
     // asmName.Version = new Version("1:0:1790:25124");
     asmName.Version = v;
    
     // Ссылка на объект - представитель класса Assembly.
     // 
    Assembly asm = null;
     try
     {
     // Загрузка сборки по "дружественному имени". 
     //asm = Assembly.Load("AssemblyForStart");
     // Путь и полное имя при загрузке частной сборки не имеют значения.
     // Соответствующие файлы должны располагаться непосредственно
     // в каталоге приложения. 
     //asm = Assembly.Load
     //(@"D:\Users\WORK\Cs\AssemblyTest\AssemblyForStart\bin\Debug\ AssemblyForStart.dll");
     //asm = Assembly.Load(asmName);
     // Если сборку организовать в виде исполняемого модуля и
     // "запихнуть" в каталог вручную – загрузится и такая сборка.
     asm = Assembly.Load("Operators00");
     }
     catch(FileNotFoundException e)
     {
     Console.WriteLine("We have a problem:" + e.Message);
     }
    
     // Итак, решено. Загрузили сборку, содержащую объявление класса xPoint.
     // Если сборка загрузилась – с ней надо что-то делать. Ясное дело,
     // надо выполнять программный код сборки.
     // Первый вариант выполнения. В сборке содержатся объявления двух классов:
     // класса xPoint и класса Class1. Мы воспользуемся объявлением класса 
     // xPoint, построим соответствующий объект - представитель этого класса, 
     // после чего будем вызывать нестатические методы - члены этого класса.
     // Все происходит в режиме ПОЗДНЕГО связывания.
     // Поэтому ни о каких ЯВНЫХ упоминаниях
     // имен типов не может быть речи.
     // Единственное явное упоминание – это упоминание
     // имени метода.
     object[] ps = {25,25};
     Type[] types = asm.GetTypes();
     // Здесь используется класс Activator! 
    object obj = Activator.CreateInstance(types[0],ps);
    
    MethodInfo mi = types[0].GetMethod("Hello");
     mi.Invoke(obj,null);
    
     // Второй вариант выполнения. Воспользуемся тем обстоятельством,
     // что загружаемая сборка не является библиотекой классов, а является
     // обычной выполнимой сборкой с явным образом обозначенной точкой
     // входа – СТАТИЧЕСКОЙ функцией Main(), которая является членом 
     // класса Class1.
     mi = asm.EntryPoint;
     // Вот, все получилось! Единственное, что я не сделал,
     // так это не смог передать в точку входа загруженной
     // сборки массива строк-параметров (смотреть на точку входа данной 
     // сборки). Ну не удалось. Потому и в сборке
     // Operators точку входа сборки объявил без параметров. 
    mi.Invoke(null,null);
     }
     }
     }

    Собрали, запустили. Получилось. Стало быть, ВСЕ ХОРОШО.

    В ходе выполнения приложения класс Assembly позволяет:

  • получать информацию о самой сборке;
  • обращаться к членам класса, входящим в сборку;
  • загружать другие сборки.
  • Разбор полетов

    В приведенном выше примере демонстрируется техника ПОЗДНЕГО СВЯЗЫВАНИЯ. Именно поэтому код, который выполняется после загрузки сборки, не содержит в явном виде информации об используемых в приложении типах. Транслятор действует в строгом соответствии с синтаксисом языка C# и просто не поймет пожелания "создать объект – представитель класса ..., который будет объявлен в сборке, которую предполагается загрузить в ходе выполнения приложения".

    Класс System.Activator

    Класс Activator – главное средство, обеспечивающее позднее связывание.

    Содержит методы, позволяющие создавать объекты на основе информации о типах, получаемой непосредственно в ходе выполнения приложения, а также получать ссылки на существующие объекты. Далее приводится список перегруженных статических методов класса:

  • CreateComInstanceFrom. Создает экземпляр COM-объекта;
  • CreateInstance. Создает объект – представитель specified-типа, используя при этом наиболее подходящий по списку параметров конструктор (best matches the specified parameters).

    Пример:

    ObjectHandle hdlSample;
     IMyExtenderInterface myExtenderInterface;
     string argOne = "Value of argOne";
     int argTwo = 7;
     object[] args = {argOne, argTwo};
     // Uses the UrlAttribute to create a remote object.
     object[] activationAttributes =
     {new UrlAttribute("http://localhost:9000/MySampleService")};
     // Activates an object for this client.
     // You must supply a valid fully qualified assembly name here.
     hdlSample = Activator.CreateInstance(
     "Assembly text name, Version, Culture, PublicKeyToken",
     "samplenamespace.sampleclass", 
    true,
     BindingFlags.Instance|BindingFlags.Public,
     null,
     args,
     null,
     activationAttributes,
     null);
     myExtenderInterface = (IMyExtenderInterface)hdlSample.Unwrap();
     Console.WriteLine(myExtenderInterface.SampleMethod("Bill"));
  • CreateInstanceFrom. Создает объект – представитель типа, специфицированного по имени. Имя специфицируется на основе имени сборки. При этом используется подходящий по списку параметров конструктор (the constructor that best matches the specified parameters).

    Пример:

    ObjectHandle hdlSample;
     IMyExtenderInterface myExtenderInterface;
     object[] activationAttributes = {new SynchronizationAttribute()};
     // Assumes that SampleAssembly.dll exists in the same directory as this assembly.
     hdlSample = Activator.CreateInstanceFrom(
     "SampleAssembly.dll",
     "SampleNamespace.SampleClass",
     activationAttributes);
     // Assumes that the SampleClass implements an interface provided by
     // this application.
     myExtenderInterface = (IMyExtenderInterface)hdlSample.Unwrap();
     Console.WriteLine(myExtenderInterface.SampleMethod("Bill"));
  • GetObjectOverloaded. Создает прокси (заместителя, представителя) для непосредственного запуска активизированного сервером объекта или XML-сервиса.
  • Сборка может быть вызвана непосредственно по имени (строковый литерал с дружественным именем сборки).

    Ничего особенного. Просто имя без всяких там расширений. Главная проблема заключается в том, что частные сборки должны предварительно включаться в раздел References (Ссылки).

    Кроме того, информация о загружаемой сборке может быть представлена в виде объекта – представителя класса AssemblyName, ссылка на который также может быть передана в качестве аргумента методу Assembly.Load().

    Версия сборки

    Эта характеристика имеется у каждой сборки, несмотря на то, что частной сборке она и ни к чему.

    Версию можно "подсмотреть" в манифесте сборки с помощью IlDasm.exe. Можно было бы для пущей крутизны кода поле Version проинициализировать непосредственно (дело хозяйское).

    И тут открываются несколько проблем:

  • в манифесте версия задается последовательностью цифр, разделенных между собой двоеточием. Однако при формировании поля Version эти цифры следует разделять точкой;
  • если сборка уникальна и среда разработки приложения ничего не знает о других версиях данной сборки – в поле Version можно спокойно забивать любые четверки чисел. Лишь бы требования формата были соблюдены;
  • две одноименных частных сборки с разными версиями в раздел References третьей сборки загрузить не получается. Одноименные файлы с одинаковым расширением в один каталог не помещаются. Невзирая на версии.
  • Следующий шаг: загружаем сборку в память.

    Используется блок try, поскольку загружаемую сборку можно и не найти.

    При загрузке сборки известно ее расположение (application directory), однако с расширением имени могут возникнуть проблемы. Действует такой алгоритм "поиска" (и называть-то это поиском как-то неудобно):

    Среда выполнения .NET пытается обнаружить файл с расширением .dll.

    В случае неудачи среда выполнения .NET пытается обнаружить файл с расширением .exe.

    В случае неудачи – предпринимаются ДРУГИЕ алгоритмы поиска.

    Файл конфигурации приложения

    Так что за ДРУГИЕ алгоритмы?

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

    Файл конфигурации – это текстовый файл со странным именем <ИмяФайла.Расширение>.config, в котором размещаются в строго определенном порядке теги, прописанные на языке XML.

    Среда выполнения .NET умеет читать XML.

    Утверждается, что если расположить частные сборки в других подкаталогах приложения, нежели bin\Debug, то с помощью файла конфигурации эти сборки исполняющая среда БЕЗ ТРУДА обнаружит. Однако не находит. Может быть, я что-то не так делаю?

    Файл AssemblyStarter.exe.config:
    
     <configuration>
     <runtime>
     <assemblyBinding xmlns="urn:schemas–microsoft–com:asm.v1" >
     <probing privatePath="XXX\YYY"/>
     </assemblyBinding>
     </runtime>
     </configuration>

    Общедоступная сборка

    Строгое имя общедоступной сборки состоит из:

  • дружественного текстового имени и "культурной" информации;
  • идентификатора версии;
  • пары "Открытый/Закрытый ключ";
  • цифровой подписи.
  • Делаем общую сборку.

  • Сначала – ЧАСТНАЯ сборка.
    using System;
     namespace SharedAssembly00
     {
     // Summary description for Class1.
    
    public class Class1
     {
     public Class1()
     {
    
     }
    
    public void f0()
     {
     Console.WriteLine("This is SharedAssembly00.f0()");
     }
     }
     }
  • Делаем пару "Открытый/Закрытый ключ".

    Для этого в Visual Studio .NET 2003 Command Prompt командной строкой вызываем утилиту – генератор ключей:

    D:\...>sn –k theKey.cnk
  • В файле сборки AssemblyInfo.cs (таковой имеется в каждом проекте, ранее не использовался) дописываем в качестве значения ранее пустого атрибута AssemblyKeyFile полный путь к созданному утилитой sn файлу (в одну строчку):
    [assembly: AssemblyKeyFile(@"D:\Users\WORK\Cs\AssemblyTest\Shared Assembly00\theKey.snk")]
  • Компилируем сборку и наблюдаем манифест сборки, в котором появляется открытый ключ. Открытый ключ размещается в манифесте сборки. Закрытый ключ хранится в модуле сборки, содержащем манифест, однако в манифест не включается. Этот ключ используется для создания цифровой подписи, которая помещается в сборку. Во время выполнения сборки среда выполнения проверяет соответствие маркера открытого ключа сборки, запрашиваемой клиентом (приложением, запускающим эту сборку), с маркером открытого ключа самой сборки общего пользования из GAC. Такая проверка гарантирует, что клиент получает именно ту сборку, которую он заказывал.
    Клиентское приложениеСборка общего пользования, установленная в GAC
    В манифесте клиента имеется ссылка на внешнюю сборку общего пользования. Маркер открытого ключа этой сборки отмечен тегом:
    ::::::::::
     .assembly extern
    SharedAssembly00
    {
    .publickeytoken =
    (90 8E D8 5E 3E 37 72 08)// ...^>7r.
    .ver 1:0:1790:37888
    }
    ::::::::::
    Манифест сборки общего пользования в GAC содержит такое же значение ключа:
    908ED85E3E377208

    Его можно увидеть при исследовани и содержимого GAC (свойства элемента)

    А закрытый ключ сборки общего пользования совместно с открытым ключом используется для создания цифровой подписи сборки и хранится вместе с подписью в самой сборке
  • Размещаем общедоступную сборку в GAC. Для этого либо используем утилиту gacutil.exe с ключом \i и именем сборки с полным путем в качестве второго параметра, либо просто перетаскиваем мышкой файл сборки в каталог, содержащий GAC. В случае успеха наблюдаем состояние GAC.
  • Сборка – там!

    Игры со сборками из GAC

    Создаем новую сборку, в которой предполагается использовать наше детище. Затем добавляем ссылку на сборку. Не все так просто. Сначала надо отыскать соответствующую .dll'ку. Наличие ключа в манифесте сборки приводит к тому, что сборка (в отличие от частных сборок) не будет копироваться в каталог запускающей сборки. Вместо этого исполняющая среда будет обращаться в GAC. Дальше – проще.

    Объявленные в сборке классы оказываются доступны запускающей сборке. Набрали аж 3 сборки общего пользования. Дело нехитрое...

    using System;
     using SharedAssembly00;
     using SharedAssembly01;
     using SharedAssembly02;
    
    namespace AssemblyStarter01
     {
    
     // Summary description for Class1.
    
    class startClass
     {
    
     // The main entry Point for the application.
    
     static void Main(string[] args)
     {
     try
     {
     SharedAssembly00.Class1 c001 = new SharedAssembly00.Class1(); c001.f0();
     SharedAssembly01.Class1 c011 = new SharedAssembly01.Class1(); c011.f0();
     SharedAssembly02.Class1 c021 = new SharedAssembly02.Class1(); c021.f0();
     }
     catch(TypeLoadException e)
     {
     Console.WriteLine("We are the problem: " + e.Message);
     }
     }
     }
     }

    Итак, подключили общедоступную сборку. Она не копируется, а остается в GAC. При создании клиента были выполнены определенные телодвижения, в результате которых клиент сохраняет информацию о свойствах располагаемых в GAC компонентов. Свойства этих компонент можно посмотреть после подсоединения данного элемента из GAC к References клиента. В частности, там есть свойство Copy local (по умолчанию установленное в false ). Это означает, что соответствующая компонента из GAC клиентом не копируется. Общую сборку можно превратить в частную сборку, если это свойство установить в true.

    Динамические сборки

    Все, о чем писалось до этого момента, – суть СТАТИЧЕСКИЕ СБОРКИ. Статические сборки существуют в виде файлов на диске или других носителях, в случае необходимости загружаются в оперативную память и выполняются благодаря функциональным возможностям класса Assembly.

    Динамические сборки создаются непосредственно в ходе выполнения приложения (статической сборки) и существуют в оперативной памяти. По завершении выполнения приложения они обречены на бесследное исчезновение, если, конечно, не были предприняты особые усилия по их сохранению на диск.

    Для работы с динамическими сборками используется пространство имен System.Reflection.Emit.

    Emit – излучать, испускать, выпускать (деньги).

    Это множество типов позволяет создавать и выполнять динамические сборки, а также ДОБАВЛЯТЬ НОВЫЕ типы и члены в загруженные в оперативную память сборки.

    Пространство имен System.Reflection.Emit содержит классы, позволяющие компилятору или инструментальным средствам создавать метаданные и инструкции промежуточного языка MSIL и при необходимости формировать на диске PE-файл. Эти классы предназначены в первую очередь для обработчиков сценариев и компиляторов.

    Список классов, структур и перечислений, входящих в пространство, прилагается.

    Классы
    КлассОписание
    AssemblyBuilder Определяет и представляет динамическую сборку
    ConstructorBuilder Определяет и представляет конструктор динамического класса
    CustomAttributeBuilder Помогает в построении пользовательских атрибутов
    EnumBuilder Описывает и предоставляет тип перечисления
    EventBuilder Определяет события для класса
    FieldBuilder Определяет и предоставляет поле. Этот класс не наследуется
    ILGenerator Создает инструкции промежуточного языка MSIL
    LocalBuilder Представляет локальную переменную внутри метода или конструктора
    MethodBuilder Определяет и предоставляет метод (или конструктор) для динамического класса
    MethodRental Позволяет быстро менять реализацию основного текста сообщения метода, задающего метод класса
    ModuleBuilder Определяет и представляет модуль. Получает экземпляр класса ModuleBuilder с помощью вызова метода DefineDynamicModule
    OpCodes Содержит поля, предоставляющие инструкции промежуточного языка MSIL для эмиссии членами класса ILGenerator (например методом Emit )
    ParameterBuilder Создает или связывает информацию о параметрах
    PropertyBuilder Определяет свойства для типа
    SignatureHelper Обеспечивает методы построения подписей
    TypeBuilder Определяет и создает новые экземпляры классов во время выполнения
    UnmanagedMarshal Представляет класс, описывающий способ маршалирования поля из управляемого в неуправляемый код. Этот класс не наследуется
    Структуры
    СтруктураОписание
    EventToken Предоставляет Token, возвращаемый метаданными для представления события
    FieldToken Структура FieldToken является объектным представлением лексемы, представляющей поле
    Label Представляет метку в потоке инструкций. Label используется вместе с классом ILGenerator
    MethodToken Структура MethodToken является объектным представлением лексемы, представляющей метод
    OpCode Описывает инструкцию промежуточного языка MSIL
    ParameterToken Структура ParameterToken является закрытым представлением возвращаемой метаданными лексемы, которая используется для представления параметра
    PropertyToken Структура PropertyToken является закрытым представлением возвращаемого метаданными маркера Token, используемого для представления свойства
    SignatureToken Предоставляет Token, возвращенный метаданными для представления подписи
    StringToken Предоставляет лексему, которая предоставляет строку
    TypeToken Представляет маркер Token, который возвращается метаданными, чтобы представить тип
    Перечисления
    ПеречислениеОписание
    AssemblyBuilderAccess Определяет режимы доступа для динамической сборки
    FlowControl Описывает, каким образом инструкция меняет поток команд управления
    OpCodeType Описывает типы инструкций промежуточного языка MSIL
    OperandType Описывает тип операнда инструкции промежуточного языка MSIL
    PackingSize Задает один из двух факторов, определяющих выравнивание занимаемой полями памяти при маршаллинге типа
    PEFileKinds Задает тип переносимого исполняемого PE-файла
    StackBehaviour Описывает, как значения помещаются в стек или выводятся из стека

    Создание, сохранение, загрузка и выполнение сборки

    //––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
     // Вот такой класс в составе однофайловой сборки DynamicAssm
     // предполагается построить в ходе выполнения сборки
     // DynamicAssemblyGenerator. 
     // public class DynamicTest
     // {
     // private string messageString;
     // // Конструктор
     // DynamicTest(string strKey)
     // {
     // messageString = strKey;
     // }
     // 
     // // Методы
     // public void ShowMessageString()
     // {
     // System.Console.WriteLine
     //      ("the value of messageString is {0}...", messageString); 
     // }
     // 
     // public string GetMessageString()
     // {
     // return messageString; 
     // }
     //
     //}
     //–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    
    using System;
     using System.Reflection;
     using System.Reflection.Emit;
     using System.Threading;
    
    namespace DynamicAssemblyGenerator
     {
    
     // AssemblyGenerator – класс, реализующий динамическую генерацию сборки.
    
    class AssemblyGenerator
     {
    public string XXX;
     public string ZZZ()
     {
     return XXX;
     }
    
    public int CreateAssm(AppDomain currentAppDomain)
     {
     // Создание сборки начинается с присвоения ей имени и номера версии.
     // Для этого используется класс AssemblyName. 
     // Определяется имя и версия создаваемой сборки.
     AssemblyName assmName = new AssemblyName();
     assmName.Name = "DynamicAssm";
     assmName.Version = new Version("1.0.0.0");
     // Создается сборка в памяти. В рамках текущего домена приложения. 
     // С использованием режима доступа,
     // который задается одним из элементов перечисления:
     // Run – динамическая сборка выполняется, но не сохраняется; 
     // RunAndSave – динамическая сборка выполняется и сохраняется;
     // Save – динамическая сборка не выполняется, но сохраняется.
     AssemblyBuilder assembly = currentAppDomain.DefineDynamicAssembly(assmName,
     AssemblyBuilderAccess.Save);
     // Создается однофайловая сборка, в которой имя единственного
     // модуля совпадает с именем самой сборки.
     ModuleBuilder module = assembly.DefineDynamicModule("DynamicAssm",
     "DynamicAssm.dll" );
     // Создается и определяется класс DynamicTest. 
     // Метод module.DefineType позволяет
     // встраивать в модуль класс, структуру или интерфейс.
     // Вторым параметром метода идет элемент перечисления.
     // Таким образом создается объект - заготовка
     // класса, который далее дополняется полями, свойствами, методами...
     TypeBuilder dynamicTestClass = module.DefineType("DynamicAssm.DynamicTest",
     TypeAttributes.Public); 
     // Объявляется данное - член класса DynamicTest. 
     // Предполагается объявить "private string messageString;"
     FieldBuilder messageStringField =
              DynamicTestClass.DefineField("messageString",
                                 Type.GetType("System.String"),
                                       FieldAttributes.Private);
     // Объекты для генерации элементов класса.
     // В данном конкретном случае используются при генерации:
     ILGenerator bodyConstructorIL; // – тела конструктора.
     ILGenerator methodIL; // – тела метода.
     // Объявляется конструктор.______________________________________
     // Предполагается объявить "DynamicTest(string strKey)..."
     Type[] constructorArgs = new Type[1];
     constructorArgs[0] = Type.GetType("System.String");
     ConstructorBuilder constructor = dynamicTestClass.DefineConstructor(
     MethodAttributes.Public,
     CallingConventions.Standard,
     constructorArgs);
     // Тело конструктора. Представляет собой IL-код,
     // который встраивается в тело конструктора посредством метода Emit,
     // определенного в классе ILGenerator
     // (см. Объекты для генерации элементов класса).
     // Метод Emit в качестве параметров использует перечисление OpCodes
     // (коды операций), которые определяют допустимые команды IL.
     bodyConstructorIL = constructor.GetILGenerator();
     bodyConstructorIL.Emit(OpCodes.Ldarg_0);
     Type objectClass = Type.GetType("System.Object"); 
    ConstructorInfo greatConstructor = objectClass.GetConstructor(new Type[0]);
     bodyConstructorIL.Emit(OpCodes.Call, greatConstructor);
     bodyConstructorIL.Emit(OpCodes.Ldarg_0);
     bodyConstructorIL.Emit(OpCodes.Ldarg_1);
     bodyConstructorIL.Emit(OpCodes.Stfld,messageStringField);
     bodyConstructorIL.Emit(OpCodes.Ret);
     // Конец объявления конструктора._______________________________
    
     // Объявление метода public string GetMessageString()__________
     MethodBuilder GetMessageStringMethod = dynamicTestClass.DefineMethod(
     "GetMessageString",
     MethodAttributes.Public,
     Type.GetType("System.Sring"),
     null);
    
     // IL_0000: ldarg.0
     // IL_0001: ldfld string DynamicAssemblyGenerator.Assembly Generator::XXX
     // IL_0006: stloc.0
     // IL_0007: br.s IL_0009
     // IL_0009: ldloc.0
     // IL_000a: ret
    
     //System.Reflection.Emit.Label label = new Label();
     // Тело метода...
     methodIL = GetMessageStringMethod.GetILGenerator();
     methodIL.Emit(OpCodes.Ldarg_0);
     methodIL.Emit(OpCodes.Ldfld,messageStringField);
     methodIL.Emit(OpCodes.Ret);
     // Конец объявления метода public string GetMessageString()__________________
    
     // Объявление метода public string ShowMessageString()_______________________
     MethodBuilder ShowMessageStringMethod = dynamicTestClass.DefineMethod(
     "ShowMessageString",
     MethodAttributes.Public,
     null,
     null);
     // Тело метода...
     methodIL = ShowMessageStringMethod.GetILGenerator();
     methodIL.EmitWriteLine("This is ShowMessageStringMethod...");
     methodIL.Emit(OpCodes.Ret);
     // Конец объявления метода public string ShowMessageString()_________________
     // Вот и завершили динамическое объявление класса.
     dynamicTestClass.CreateType();
     // Остается его сохранить на диск.
     assembly.Save("DynamicAssm.dll");
     return 0;
     }
    
    
    static void Main(string[] args)
     {
     // Создается и сохраняется динамическая сборка.
     AssemblyGenerator ag = new AssemblyGenerator();
     ag.CreateAssm(AppDomain.CurrentDomain);
    
     // Для наглядности! создаются НОВЫЕ объекты и заново добывается
     // ссылка на текущий домен приложения.
     // Теперь – дело техники. Надо загрузить и выполнить сборку.
     // Делали. Умеем!
     AppDomain currentAppDomain = Thread.GetDomain();
     AssemblyGenerator assmGenerator = new AssemblyGenerator(); 
    assmGenerator.CreateAssm(currentAppDomain);
    // Загружаем сборку.
     Assembly assm = Assembly.Load("DynamicAssm");
     // Объект класса Type для класса DynamicTest.
     Type t = assm.GetType("DynamicAssm.DynamicTest");
    
     // Создается объект класса DynamicTest и вызывается конструктор 
     // с параметрами.
     object[] argsX = new object[1];
     argsX[0] = "Yes, yes, yes–s–s–s!";
     object obj = Activator.CreateInstance(t, argsX);
    
    MethodInfo mi;
     // "От имени" объекта - представителя класса DynamicTest
     // вызывается метод ShowMessageString.
     mi = t.GetMethod("ShowMessageString");
     mi.Invoke(obj,null);
    
     // "От имени" объекта - представителя класса DynamicTest
     // вызывается метод GetMessageString.
     // Этот метод возвращает строку, которая перехватывается
     // и выводится в окне консольного приложения.
     mi = t.GetMethod("GetMessageString");
     //!!!//mi.Invoke(obj,null);//Этот метод не вызывается. Криво объявился? // 
     }
     }
     }
    Вернуться к учебному плану