субота, 1 вересня 2012 р.

C#.Концепція та синтаксис. Частина 6


ДОДАТКИ

У середовищі .NET код групується у складені модулі - елементарні одиниці в середовищі аплікації. Складений модуль є набором ресурсів і типів, інтегрованих в єдиний логічний об' єкт, який володіє визначеними функціональними можливостями. Складений модуль містить маніфест, метадані типів, код MSIL та ресурси (не всі ці складові є необхідними).
Модуль може налічувати один або кілька файлів. Один з цих файлів містить маніфест модуля - частину метаданих (даних, які описують дані), в яких перелічено вміст складеного модуля:
         Версія складеного модуля.
         Культура. Це поле присутнє у маніфесті, якщо модуль призначено для підтримки глобалізації та містить реалізацію функцій для деякої конкретної культури.
         Інформація про стійке ім'я. Якщо модулю надано сильне ім'я, це поле містить відкритий ключ провайдера модуля.
          Список файлів. Кожен файл модуля ідентифікується за кеш-кодом, що робить модулі значно захищенішими від несанкціонованої модифікації чи заміни файлів.
         Інформація про типи. Якщо зі складеного модуля експортують типи, у маніфесті перелічуються файли, які містять оголошення та реалізацію відповідних типів.
         Інформація про зовнішні складені модулі. Для кожного модуля, який використовує цей складений модуль, зазначають ім'я, версію, культуру та відкритий ключ.
         Набір вимог на права. Якщо модуль використовує сторонній код або ресурси, то маніфест містить вимоги на право такого використання.
Типи даних є унікальними всередині складеного модуля. За його межами імена типів уточнюють назвою складеного модуля.
Права доступу надають (або не надають) для модуля загалом.
Перевірка версій здійснюється на рівні модуля. У ньому можуть бути вказані версії інших модулів, необхідні зазначеному модулю для роботи. Файли, які формують модуль, версій не мають.
Складений модуль може мати лише одну точку входу: Main, WinMain або DllMain.
Для перегляду вмісту модуля можна використати утиліту ILDasm.exe.
За замовчуванням під час компіляції програми утворюється приватний складений модуль, доступ до якого має лише одна аплікація. Такий модуль розташовується в каталозі аплікації або його підкаталозі. Щоб до приватного модуля мала доступ інша програма, необхідно утворити копію модуля в каталозі програми.
На відміну від приватного, розподілений складений модуль може використовуватися декількома аплікаціями, розташованими в одній файловій системі. Розподілений модуль необхідно забезпечити унікальним ім'ям і розташувати в глобальному кеші модулів (область файлової системи в каталозі WinNT\Assembly).
Розгорнути модуль в глобальному кеші можна з допомогою інструмента .NET Framework Configuration, утиліт Al.exe та gacutil. exe або інсталятора, який працює з глобальним кешем модуля. У будь-якому випадку необхідно виконати такі дії:
          Створення криптографічної пари. З цією метою використовують утиліту розподілених імен sn. exe. Наприклад, команда sn -k newkey.snk утворить файл newkey. snk, який міститиме персональний і відкритий ключі.
         Задання стійкого імені. У файлі AssemblyInfo.cs проекту потрібно надати атрибуту assembly: AssemblyKeyFile значення назви файла, який містить криптографічну пару, і перекомпілювати проект.
         Розташування складеного модуля в кеші. Доцільно використати утиліту gacutil.exe. Наприклад: gacutil /irMyAssembly.dll.


Атрибути складеного модуля використовують для опису властивостей модуля.
 Перелічимо основні атрибути складених модулів.
Атрибут                                   Зміст
Атрибути ідентифікації

AssemblyCultureAttribute
Визначає культури, які підтримуються

модулем
AssemblyFlagsAttribute
Атрибут-перелік визначає, які типи спів­

існування декількох версій підтримує

модуль
AssemblyVersionAttribute
Версія моду.ля
Інформаційні атрибути

AssemblyCompanyAttribute
Назва компанії, яка створила модуль
AssemblyCopirightAttribute
Авторські права на модуль
AssemblyFileVersion-
Зручна для читання інформація про
Attribute
версію модуля
AssemblyInfomational-
Додаткова інформація про версію
VersionAttribute

AssemblyProductAttribute
їм.'я продукту, якому належить модуль
AssemblyTrademarkAttribute
Інформація про торгову марку, яка

представ.ляє модуль
Атрибути маніфеста

AssemblyDefaultAlias-
Альтернативне ім'я модуля. Викорис­
Attribute
товують, якщо повне ім'я модуля є

ідентифікатором GUID
AssemblyDescription-
Короткий опис модуля
Attribute

AssemblyTitleAttribute
Зручне для читання ім'я модуля. Може

містити пробіли
Атрибути стійких імен

AssemblyDelaySignAttribute
Ознака використання відкладеного

підпису
AssemblyKeyFileAttribute
Ім'я файла, який містить інформацію про

криптографічний ключ
AssemblyKeyNameAttribute
Ім'я контейнера, який містить

інформацію про криптографічний ключ


.NET передбачає утворення власних атрибутів. Значення атрибутів можна змінювати програмно. В імені атрибута підстрічку Attribute можна не зазначати. Наприклад, терміни AssemblyKeyNameAttribute та AssemblyKeyName є синонімами.


Для надання значення атрибуту складеного модуля у C# використовують такий синтаксис:
[assembly:AttributeName("Value")]
Наприклад:
using System.Reflection;
[assembly : AssemblyVersionAttribute ("1.0.0.1") ]
Усі типи проектів в VS.NET утворюють складені модулі у вигляді виконуваного файла (EXE) або бібліотеки (DLL). Складені модулі можна також утворювати безпосередньо компілятором командної стрічки scs. Додатково компілятор дає змогу утворювати прості модулі - DLL без атрибутів складеного модуля. Простий модуль також має маніфест, однак всередині маніфеста відсутня позиція .assembly.
Наведемо деякі приклади використання компілятора csc.



Код
csc.exe File.cs
csc.exe /t:library File.cs
csc.exe /out:MyFile.exe File.cs
csc.exe /optimize /out:MyFile.exe *.cs
csc.exe /t:module /out:MyModule.dll File.sc
csc.exe /t:library /addmodule: MyModule.dll File.cs csc.exe /t:library /r: ExternalAsm.dll File.cs

Зміст

Компіляція файла File.cs утворює складений модуль File.exe Компіляція файла File.cs утворює складений модуль File.dll Компіляція файла File.cs утворює складений модуль MyF ile.exe Компіляція усіх С#-файлів у поточному каталозі з оптимізацією утворює складений модуль MyFile. exe
Компіляція файла File.cs утворює простий модуль MyModule. dll
Компіляція файла File.cs утворює складений модуль File. dll, до якого додається модуль MyModule. dll Компіляція файла File.cs утворює складений модуль File.dll, до якого додається інформація про використання зовнішнього модуля ExternalAsm.dll


Компілятор csc має значну кількість опцій. їхній перелік і зміст можна отримати з допомогою команди csc.exe /help (або csc.exe /?).
Для утворення складеного модуля з декількох файлів можна використати утиліту компонування al. Наприклад, команда
al.exe M1.netmodule M2.netmodule /embed:My.bmp /main:M1.Main /out:MyApp.exe /t:exe
утворює файл складеного модуля MyApp.exe. Модуль складається з двох простих модулів і графічного ресурсу My. bmp, який додано з допомогою ключа /embed. Ключ /main зазначає повну назву точки входу (клас і метод).
Поряд із програмним кодом, який представляє логіку виконання програми, складені модулі .NET містять додаткову інформацію - метадані. Метадані складаються з атрибутів. У цьому розділі розглянемо роботу з вбудованими атрибутами, створення власних атрибутів та механізм отримання інформації про складений модуль і типи, які він містить, у процесі виконання.
.NET Атрибути
Атрибут - це клас, породжений від абстрактного базового класу System.Attribute. Атрибут може містити як інформацію стану, так і функціональність. Він може асоціюватися (залежно від типу) з методом, класом чи складеним модулем загалом.
До одного елемента програми можна застосувати декілька атрибутів. При цьому атрибути можна розмістити в середині однієї пари квадратних дужок, розділюючи їх комами.
Оскільки всі атрибути є предками класу Attribute, доцільно перелічити його методи:
Метод____________________ Зміст________________________________________
GetCustomAttribute           Повертає атрибут заданого класу та заданого типу


IsDefaultAttribute
IsDef ined
Match
Повертає true, якщо екземпляр є атрибутом за замовчуванням для класу
Показує, чи застосовано заданий атрибут до заданого класу
Повертає true, якщо два атрибути збігаються
GetCustomAttributes         Повертає масив атрибутів заданого класу_____



Клас Attribute має також властивість Typeld, яка при реалізації в класі-нащадку повертає унікальний ідентифікатор атрибута.
Середовище .NET містить тисячі атрибутів. Однак найкориснішими є лише декілька. Розглянемо деякі з них.
Атрибут System.ObsoleteAttribute призначений для позначення застарілих фрагментів коду та виведення пропозиції про їхню заміну:
[Obsolete("Mетод RandomDbl має дещо
ефективніший аналог newRandomDbl", false)]
public static double RandomDbl { Random r = new Random(); return r.NextDouble();
}
Нагадаємо, що підстрічку Attribute у назвах атрибутів можна не писати.
Атрибут Obsolete приймає два параметри. Перший - стрічкове повідомлення про застарілий фрагмент коду. Другий - логічне значення. Якщо це false (значення за замовчуванням), то компілятор видасть попередження про застарілий код, однак дасть змогу успішно завершити процес компіляції. Якщо ж true, то компіляція завершиться помилкою.
Атрибут System.Diagnostics.ConditionalAttri- bute дає змогу керувати умовною компіляцією. Цей атрибут можна використовувати з довільним методом, який повертає значення типу void. Якщо означеной відповідний символ, то компілятор не буде компілювати цей метод. А також не компілюватиме і всі оператори, які містять звертання до методу. Щоб досягнути цього результату з допомогою директив препроцесора #if...#then, їх необхідно розташувати у всіх відповідних місцях коду.
Атрибут DlllmportAttribute використовують для доступу до функцій Windows API або інших функцій, реалізованих в DLL. Цей атрибут оголошений у просторі імен System. Runtime.InteropServices. Наведемо приклад використання атрибута:
[Dlllmport("KERNEL32.DLL",
EntryPoint="MoveFileW"] public static extern bool MoveFile(
string src, string dst);
Функція API MoveFile означена у файлі Kernel.dll, отож ім'я цього файла присутнє в аргументах атрибута.
Ще один важливий атрибут SerializableAttribute (простір імен System.Runtime.Serialization) ми розглянули в розділі Серіалізація.
.NET передбачає утворення власних атрибутів. Клас, який реалізовує новий атрибут, повинен успадковувати клас
System.Attribute. Наприклад:
public class MyAttribute: Attribute { string sNote; public string Note { get {return sNote;}
}
public MyAttribute(string Note) { sNote = Note;
}
}
Тепер можна атрибут MyAttribute застосувати до потрібних елементів:
[MyAttribute("Категорія A")] public class Classl { } [MyAttribute("Категорія B")] public class Class2 { }
Значення атрибутів будуть розміщені в коді MSIL програми. Ці значення можна переглянути з допомогою дизасемблера ILDasm.exe. Вони також є доступними для довільної аплікації, яка може прочитати MSIL-структуру.
Користувацький атрибут за замовчуванням можна застосувати до довільного елемента коду. Однак .NET дає змогу обмежити область застосування за допомогою атрибута AttributeUsage, який має такий синтаксис:
[AttributeUsage( validon,
AllowMultiple = allowmultiple, Inherited = inherited )]
Тут validon - позиційний параметр, який визначає область застосування користувацького атрибута комбінацією прапорців
AttributeTargets. Наприклад:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate]
Іменований параметр allowmultiple типу bool визначає, чи можна атрибут використовувати багаторазово. За замовчуванням він має значення false.
Іменований параметр inherited типу bool визначає, чи значення атрибута успадковуватимуть дочірні класи. За замовчуванням він також має значення false.
Для читання значень атрибутів під час виконання програми можна використати статичний метод GetCustomAttribute класу Attribute:
MyAttribute a =
(MyAttribute)Attribute.GetCustomAttribute( typeof(Classl),typeof(MyAttribute));
Метод GetCustomAttribute сильно перевантажений. У наведеному коді метод приймає два параметри. Перший - це тип елемента, атрибут якого шукаємо. Другий - це тип шуканого атрибута. Метод повертає значення типу Attribute, яке необхідно явно привести до потрібного типу. Якщо для зазначеного елемента не знайдено атрибут заданого типу, то метод поверне значення null.
Якщо атрибутів заданого типу декілька, то для читання значень атрибутів можна використати статичний метод
GetCustomAttributes класу Attribute. Наприклад:
object[] attrs =
Attribute.GetCustomAttributes(typeof(Class1) ) ; foreach (object attr in attrs) { //використання атрибута attr
}
Відображення - це механізм отримання під час виконання програми інформації про складений модуль і типи, які в ньому розташовані. Поряд з терміном відображення часто використовують термін рефлексія (Reflection).
.NET платформа має широкі можливості доступу до інформації про тип того чи іншого об'єкта програми, а також генерування нового програмного коду в процесі виконання існуючого. Інструменти для роботи з метаданими в режимі виконання програми зосереджені у просторі імен System.Reflection. Цей простір імен містить чимало класів, інтерфейсів, структур, делегатів і переліків. Ми ж перелічимо лише деякі з класів:
Клас
Опис
Assembly
Представляє окремий складений модуль
ConstructorInfo
Представляє конструктор класу
EventInfo
Представляє подію класу
FieldInfo
Представляє поле класу
MemberInfo
Представляє член класу
MethodBase
Представляє метод або конструктор
MethodInfo
Представляє метод класу
Module
Представляє окремий модуль коду
ParameterInfo
Представляє параметр компонента
PropertyInfo
Представляє властивість класу

Ще один важливий для рефлексії клас Type, який представляє тип загалом, розташований у просторі імен System.
Розглянемо код:
Assembly asm = Assembly.GetExecutingAssembly(); //перебір всіх типів, оголошених у модулі string s = "";
foreach(Type t in asm.GetTypes()) { s += t.Name + "\n"; s += " Поля\и";
foreach(FieldInfo f in t.GetFields()) { s += " " + f.Name + "\n";
}
s += " Методи\п";
foreach(MethodInfo m in t.GetMethods()) { s += " " + m.Name + "\n";
}
s += " Властивості^";
foreach(PropertyInfo p in t.GetProperties ()) { s += " " + p.Name + "\n";
}
}
В результаті виконання коду стрічка s міститиме список типів у складеному модулі, їхніх полів, методів і властивостей.
Статичний метод GetExecutingAssembly класу Assembly повертає складений модуль (об'єкт типу Assembly), що містить код, активний в даний момент часу.
Метод GetTypes об'єкта Assembly повертає колекцію усіх типів, оголошених у складеному модулі. Ця колекція містить об'єкти типу Type.
Методи GetFields, GetMethods і GetProperties класу Type повертають колекції об'єктів з інформацією про поля, методи та властивості типу. Ці об'єкти мають тип, відповідно, FieldInfo, MethodInfo і PropertyInfo. Подібно для обраного типу можна отримати також інформацію про конструктори (метод GetConstructors, об'єкти типу ConstructorInfo), події (GetEvents, EventInfo), інтерфейси (Get Interfaces, Type) ), члени типу (GetMembers, MemberInfo) та багато іншого.
Метод GetCustomAttribute класу Assembly можна використати для пошуку позначених класів або членів класів. Наприклад,
foreach(MethodInfo m in t.GetMethods()) { MyAttribute a = (MyAttribute) Attribute.
GetCustomAttribute(m, typeof(MyAttribute)); if(m != null) {
//метод позначений атрибутом MyAttribute //виконуємо якісь дії
}
}
З цією ж метою можна використати і метод
GetCustomAttributes, успадкований класом Type від базового класу MemberInfo:
foreach(Type t in asm.GetTypes()) {
MemberInfo[] myMembers = t.GetMembers(); for(int i = 0; i < myMembers.Length; i++){ object[] myAttributes =
myMembers[i].GetCustomAttributes(true); if(myAttributes.Length > 0){
for(int j=0; j<myAttributes.Length; j++) //якісь дії з аналізу та використання атрибута
}
}
}
З допомогою класів простору System.Reflection ми можемо під час виконання програми отримати не лише перелік складених модулів, типів і членів класів, а й інформацію про розмірності масивів, параметри методів, ієрархію типів, модифікатори та багато чого іншого.
Використання рефлексії для пізнього зв'язування
Механізм відображення дає змогу аплікації виконати код, який стане їй відомим лише під час виконання.
Припустимо, що аплікація, залежно від специфіки роботи користувача, повинна використовувати різні версії деякого метода. Одна з можливих схем реалізації такої структури програми є такою:
         для кожної версії розробляють відповідний складений модуль;
          потрібний клас і метод позначають атрибутами;
         у конфігураційному файлі аплікації зазначають ідентифікатори складеного модуля;
          з конфігураційного файла зчитують ідентифікатори складеного модуля та відповідний модуль завантажують у процес (за допомогою статичних методів Load чи LoadFrom класу Assembly);
          здійснюють пошук потрібного методу за атрибутами, як описано в попередньому розділі;
          запускають метод на виконання.
Конфігураційний файл аплікації (або інші джерела інформації) може містити назву складеного модуля, класу та методу. Тоді нема потреби у використанні атрибутів.
Припустимо, що модуль Sample.Assembly.dll у просторі імен ClassLibrary1 містить клас Class1. Один з методів цього класу має сигнатуру
public int Methodl(int aCount);
Наведемо код, який демонструє запуск методу Method1 на виконання.
Assembly asm;
asm = Assembly.LoadFrom
("c:\\Sample.Assembly.dll"); Type t = asm.GetType("ClassLibrary1.Class1"); MethodInfo m;
m = t.GetMethod("Method1"); object[] args = new object[1]; args [0] = 5;
object obj = Activator.CreateInstance(t); int i = (int)m.Invoke(obj, args);
MessageBox.Show (i.ToString());
Метод LoadFrom завантажує модуль. Метод GetType повертає об'єкт типу Type з описом типу, заданого стрічковим параметром. Зазначимо, що цей параметр повинен містити повну назву типу. У прикладі - це назва простору імен і назва класу.
Метод GetMethod повертає об'єкт класу MethodInfo з описом методу Methodl класу Classl. Цей об'єкт дає змогу отримати всю загальнодоступну інформацію про метод.
Оскільки сигнатура методу нам відома, ми відразу розпочинаємо підготовку даних для виклику методу: утворюємо та ініціалізуємо масив аргументів. У складніших випадках доцільно використати клас ParameterInfo.
Щоб викликати нестатичний метод класу, потрібен екземпляр класу. Такий екземпляр утворюємо з допомогою методу CreateInstance класу Activator. Цей клас призначений для утворення екземплярів типів і містить лише статичні методи:
Метод______________________ Опис______________________________________
CreateComInstanceFrom                           За іменем файла утворює екземпляр COM-типу 
CreateInstance                                       Утворює екземпляр заданого типу
CreateInstanceFrom                                 Утворює екземпляр заданого типу, оголошеногов заданому                                          
                                                        файлі
GetObject                                                           Утворює проксі-об'єкт для активованого  віддаленого об'єкта     
З цього переліку бачимо, що в наведеному вище прикладі можна було б скоротити код, використавши метод CreateInstanceFrom замість CreateInstanceFrom.
Після утворення екземпляра класу Classl можемо активізувати його методи. Однак при пізньому зв'язуванні компілятор не має інформації про клас Classl. Отож звичний синтаксис obj .Methodl виклику методу не підходить. Викликати метод можна опосередковано, використовуючи метод Invoke класу MethodBase (або класів-нащадків). Один з варіантів цього методу має сигнатуру
public object Invoke( object obj, object[] parameters);


Параметр obj визначає об'єкт, для якого активізують метод (представлений екземпляром MethodBase), а параметр parameters задає список аргументів. Метод Invoke повертає результат типу object, тому необхідне явне зведення до потрібного типу:
(int)m.Invoke(obj, args);

Немає коментарів:

Дописати коментар