Нові засоби C # в. NET Framework 4, HTML, XML, DHTML, Інтернет-технології, статті

З моменту своєї появи в 2002 році мова програмування C # був значно вдосконалений, щоб програмісти могли писати більш чіткий і простий у супроводі код. Покращення включали такі мовні засоби, як узагальнені типи, значимі типи, що допускають null-значення, лямбда-виразу, методи ітератора, часткові класи і довгий список інших корисних мовних конструкцій. І найчастіше ці зміни супроводжувалися введенням відповідної підтримки у бібліотеки Microsoft. NET Framework.


Ця тенденція в бік підвищення зручності у використанні зберігається і в C # 4.0. Внесені до нього зміни набагато спрощують виконання поширених завдань, у тому числі роботу з узагальненими типами, взаємодія із застарілими інфраструктурами та операції з моделями динамічних об'єктів. Мета цієї статті – дати вам високорівнева огляд всіх цих нових засобів. Я почну з узагальненої варіантності (generic variance), а потім розгляну засоби взаємодії з застарілими інфраструктурами і динамічними об'єктами.


Ковариантности і контраваріантность


Суть ковариантности (covariance) і контраваріантності (contravariance) найкраще показати на прикладі, а кращий приклад вже присутня в самій інфраструктурі. IEnumerable <T> і IEnumerator <T> в System.Collections.Generic представляють відповідно об'єкт, який є послідовністю T, і перечіслітель (або ітератор), що виконує перебір послідовності. Ці інтерфейси вже давно і інтенсивно використовуються, так як вони підтримують реалізацію конструкції циклу foreach. У C # 3.0 вони стали ще важливіше з огляду на те, що вони відіграють центральну роль в LINQ і LINQ to Objects, – це. NET-інтерфейси для подання послідовностей.


Якщо у вас є ієрархія класів, скажімо, з типом Employee і типом Manager, похідним від Employee (менеджери – адже теж наймані працівники), то, як по-вашому, що робить такий код?



 IEnumerable <Manager> ms = GetManagers ();
IEnumerable<Employee> es = ms;

Виглядає так, що послідовність Manager слід інтерпретувати як послідовність Employee. Але в C # 3.0 це привласнення завершиться невдачею; компілятор повідомить, що перетворення типів відсутня. Зрештою, у нього немає ні найменшого уявлення про належну в даному випадку семантиці IEnumerable <T>. Це може бути будь-який інтерфейс, тому, якщо взяти довільний інтерфейс IFoo <T>, з якого дива IFoo <Manager> повинен бути правильніше, ніж IFoo <Employee>?


Проте в C # 4.0 це привласнення спрацює, так як IEnumerable <T> (поряд з кількома іншими інтерфейсами) змінився через нову в C # підтримки ковариантности параметрів-типів.


IEnumerable <T> наділений більш специфічними правами, ніж довільний IFoo <T>, оскільки – хоч це і не очевидно з першого погляду – члени, що використовують параметр-тип T (GetEnumerator в IEnumerable <T> і властивість Current в IEnumerator <T>), насправді застосовують T тільки в позиції, що повертається. А значить, ви лише отримуєте Manager з послідовності, але ніколи не ставите його туди.


А тепер для контрасту згадайте List <T>. Зробивши List <Manager> замінником List <Employee>, ви влаштували б катастрофу зважаючи наступного:



List<Manager> ms = GetManagers();
List<Employee> es = ms; // Suppose this were possible
es.Add(new EmployeeWhoIsNotAManager()); // Uh oh

Приклад показує: як тільки ви починаєте думати, начебто дивитеся на List <Employee>, ви можете вкласти будь-якого співробітника. Але даний список насправді є List <Manager>, тому вставка співробітника, відмінного від менеджера, повинна завершитися помилкою. Інакше ви втратили б безпеку системи типів. List <T> не може бути коваріантний в T.


У такому випадку це нове мовне засіб в C # 4.0 полягає в можливості визначати типи зразок нового IEnumerable <T>, який допускає перетворення між собою за умови, що його параметри-типи мають якесь відношення один до одного. Ось що використовували розробники. NET Framework, які писали IEnumerable <T>, і ось як виглядав їх код (у спрощеному вигляді, звичайно):



 public interface IEnumerable <out T> {/ * … * /}

Зверніть увагу на ключове слово out, модифікуючу визначення параметра-типу T. Компілятор, зустрічаючи такий модифікатор, буде позначати T як коваріантний і перевіряти, щоб у визначенні цього інтерфейсу всі випадки використання T були коректні (іншими словами, щоб такі параметри використовувалися тільки як вихідні, – ось чому було обрано ключове слово out).


Чому ж це назвали ковариантности? Ну, найлегше це зрозуміти, малюючи зв'язку стрілками. Конкретніше, візьмемо типи Manager і Employee. Оскільки між цими класами існує зв'язок спадкування, допускається неявне посилальне перетворення (implicit reference conversion) з Manager в Employee:


Manager → Employee


І тепер через анотації T в IEnumerable <out T> існує ще і неявне посилальне перетворення з IEnumerable <Manager> в IEnumerable <Employee>. Ось для чого надається анотація:


IEnumerable <Manager> → IEnumerable <Employee>


Це називають ковариантности тому, що стрілки в кожному з двох прикладів вказують в одному напрямку. Ми почали з двох типів: Manager і Employee. Ми зробили з них нові типи: IEnumerable <Manager> і IEnumerable <Employee>. Нові типи перетворюються так само, як і вихідні.


Під контраваріантностью увазі протилежне. Ймовірно, ви вже здогадалися, що це можливо, коли параметр-тип T використовується тільки як вхідний, і ви абсолютно праві. Наприклад, у просторі імен System міститься інтерфейс IComparable <T> з єдиним методом CompareTo:



 public interface IComparable <in T> {
bool CompareTo(T other);
}

Якщо у вас є IComparable <Employee>, ви повинні мати можливість обробляти його так, ніби це IComparable <Manager>, оскільки єдине, що ви можете зробити, – передати Employee в інтерфейс. А раз менеджер є і співробітником, така передача повинна працювати, і вона працює. У цьому випадку параметр-тип T модифікується ключовим словом in, і в наступному прикладі функціонує коректно:



 IComparable <Employee> ec = GetEmployeeComparer ();
IComparable<Manager> mc = ec;

Це називають контраваріантностью тому, що на цей раз стрілка вказує у зворотному напрямку:


Manager → Employee
IComparable<Manager> ← IComparable<Employee>


Отже, додаючи ключове слово in або out при визначенні параметра-типу, ви вільні виконувати додаткові перетворення. Але деякі обмеження все ж таки є.


По-перше, все це працює тільки з узагальненими інтерфейсами і делегатами. Ви не можете таким чином оголосити узагальнений параметр-тип в будь-якому класі або конструкції struct. Делегати дуже схожі на інтерфейси з єдиним методом, а класи в будь-якому випадку часто були б непридатні для такої обробки через полів. Всі поля в узагальненому класі можна вважати як вхідним, так і вихідним – залежно від того, пишете ви в це поле або зчитуєте з нього. Якщо ці поля включають параметри-типів, то такі параметри не можуть бути ні коваріантними, ні контраваріантнимі.


По-друге, за наявності інтерфейсу або делегата з коваріантний або контраваріантним параметром-типом ви отримуєте право на нові перетворення цього типу лише за умови, що аргументи типу в разі використання інтерфейсу (а не його визначення) є посилальними типами. Наприклад, так як int – значимий тип, IEnumerator <int> не можна перетворити в IEnumerator <object>, хоча здається, що це слід було б дозволити:


IEnumerator <int>    IEnumerator <object>


Причина такої поведінки в тому, що подання типу повинно зберегтися після перетворення. Якби було дозволено перетворення int в object, виклик властивості Current став би в підсумку неможливий, так як значущий тип int має інше уявлення в стеку в порівнянні з об'єктної посиланням. Однак все посилальні типи представляються в стеці однаково, тому лише над аргументами посилальних типів допускаються ці додаткові перетворення.


Досить імовірно, більшість розробників на C # буде із задоволенням користуватися цим новим мовним засобом – це дозволить їм застосовувати більше перетворень над типами, вбудованими в інфраструктуру, і рідше отримувати помилки компіляції при використанні деяких типів з. NET Framework (у тому числі IEnumerable <T>, IComparable <T>, Func <T>, Action <T>). І фактично кожен, хто проектує бібліотеку з узагальненими інтерфейсами і делегатами, вільний застосовувати нові параметри-типи з in і out, полегшуючи життя своїм користувачам.


До речі, ця функціональність підтримується і виконуючою середовищем, але ця підтримка була в ній завжди. Однак протягом кількох версій вона залишалася незатребуваною тому, що жодна мова не використовував її. Крім того, попередні версії C # допускали деякі обмежені перетворення, які були контраваріантнимі. Точніше, вони дозволяли створювати делегати з методів, що мають сумісні повертаються типи. До того ж типи масивів завжди були коваріантними. Ці можливості відрізняються від нових в C # 4.0, який реально дозволяє визначати власні типи, які є коваріантними і контраваріантнимі в деяких з їх параметрів-типів.


Динамічна диспетчеризація


Тепер перейдемо до засобів взаємодії в C # 4.0 і почнемо, мабуть, з самого головного зміни.


У C # введена підтримка динамічного пізнього зв'язування (dynamic late-binding). Ця мова завжди був строго типізований і залишається таким в версії 4.0. У Microsoft упевнені, що це робить C # простим у використанні, швидким і відповідним для будь-якої роботи, якою займаються. NET-програмісти. Але іноді виникає необхідність у взаємодії з системами, не заснованими на. NET.


До вирішення цієї проблеми традиційно підходили принаймні двома способами. Перший полягав в простому імпорті зовнішньої моделі прямо в. NET як проксі. Механізм COM Interop якраз і є одним з прикладів цього підходу. В. NET Framework цю стратегію застосовували з моменту першого випуску, при цьому використовували утиліту TLBIMP, яка створює нові. NET-типи проксі, доступні безпосередньо з C #.


LINQ-to-SQL, що поставляється з C # 3.0, містить утиліту SQLMETAL, яка імпортує існуючу базу даних в C #-класи проксі для використання з запитами. Крім того, є утиліта, що імпортує WMI-класи (Windows Management Instrumentation) в C #. Багато технологій дозволяють писати код на C # (часто з атрибутами), а потім здійснювати взаємодію з допомогою цього коду для виконання зовнішніх операцій, застосовуючи, наприклад, LINQ-to-SQL, Windows Communication Foundation (WCF) і серіалізацию.


Другий підхід повністю відкидає систему типів C #: ви вбудовуєте в свій код рядка і дані. Саме так ви поступаєте всякий раз, коли пишете код, який, скажімо, викликає якийсь метод JScript-об'єкта, або коли ви вбудовуєте SQL-запит у свій додаток на основі ADO.NET. Ви робите це, навіть коли відкладаєте зв'язування до періоду виконання, використовуючи механізм відображення, хоча в даному випадку взаємодія здійснюється з. NET.


Ключове слово dynamic в C # усуває всі складнощі, пов'язані зі згаданими підходами. Почнемо з простого прикладу – відображення. Зазвичай його застосування вимагає написання уйми стереотипного інфраструктурного коду на зразок показаного нижче:



object o = GetObject();
Type t = o.GetType();
object result = t.InvokeMember(“MyMethod”,
BindingFlags.InvokeMethod, null,
o, new object[] { });
int i = Convert.ToInt32(result);

Тепер замість виклику методу MyMethod якогось об'єкта з використанням відображення ви можете за допомогою ключового слова dynamic повідомити компілятору обробляти o як динамічний і відкласти весь аналіз до періоду виконання. Код, який робить це, виглядає так:



dynamic o = GetObject();
int i = o.MyMethod();

Цей код працює і виконує зовсім те ж саме, але зверніть увагу, наскільки він простіше і чіткіше.


Цінність цього більш лаконічного і простого синтаксису, ймовірно, виявиться набагато наочніше, якщо ви подивитеся на клас ScriptObject, який підтримує операції з JScript-об'єктом. У цьому класі є метод InvokeMember з великою кількістю параметрів; виняток становить Silverlight, де аналогічний метод називається Invoke (зверніть увагу на відмінність в іменах) з меншим числом параметрів. Ні один з них не годиться для виклику методу IronPython-або IronRuby-об'єкта, а також будь-якого об'єкта, що не відноситься до C #, з яким вам може знадобитися взаємодію.


На додаток до об'єктів з динамічних мов ви знайдете найрізноманітніші моделі даних, які за своєю природою є динамічними і мають різні API, що підтримують їх, наприклад HTML DOM, System.Xml DOM і модель XLinq для XML. COM-об'єкти теж найчастіше є динамічними.


Фактично C # 4.0 пропонує спрощене узгоджене уявлення динамічних операцій. Щоб скористатися цією перевагою, вам треба лише вказати, що дане значення динамічне, і аналіз всіх операцій над цим значенням буде відкладений до періоду виконання.


У C # 4.0 тип dynamic є вбудованим і позначається спеціальним псевдо ключовим словом. Однак dynamic відрізняється від var. Змінні, оголошені з var, насправді підпорядковуються суворої типізації – просто тим самим програміст перекладає тягар визначення їх типів на компілятор. Коли програміст використовує dynamic, компілятору не відомий тип, який буде присвоєно даної змінної, – це завдання відкладається до періоду виконання.


Dynamic та DLR


Інфраструктура, що підтримує ці динамічні операції в період виконання, називається Dynamic Language Runtime (DLR). Це нова бібліотека. NET Framework 4, виконувана CLR, як і будь-які інші керовані бібліотеки. DLR служить посередником при виконанні кожної динамічної операції між мовою, з якого ініційована ця операція, і об'єктом, до якого вона застосовується. Якщо динамічна операція не оброблена об'єктом, в якому вона протікає, один з компонентів компілятора C # обробляє зв'язування. Спрощена і неповна схема архітектури показана на рис. 1.



Рис. 1. DLR виконується поверх CLR


Одне цікаве якість динамічної операції, такий як виклик динамічного методу, полягає в тому, що приймає об'єкт має можливість вбудовувати себе в прив'язку в період виконання і в результаті може повністю визначати семантику будь-якої конкретної динамічної операції. Наприклад, погляньте на наступний код:



dynamic d = new MyDynamicObject();
d.Bar(“Baz”, 3, d);

Якщо MyDynamicObject був визначений, як показано тут, то ви можете уявити, що станеться:



class MyDynamicObject : DynamicObject {
public override bool TryInvokeMember(
InvokeMemberBinder binder,
object[] args, out object result) {

Console.WriteLine(“Method: {0}”, binder.Name);
foreach (var arg in args) {
Console.WriteLine(“Argument: {0}”, arg);
}

result = args[0];
return true;
}
}


Фактично цей код виведе:



Method: Bar
Argument: Baz
Argument: 3
Argument: MyDynamicObject

Так як змінна d оголошена з динамічним типом, код, що використовує мій примірник MyDynamicObject, в кінцевому рахунку не бере участі на етапі компіляції у перевірці операцій, в яких бере участь d. Застосування ключового слова dynamic означає: "Я не знаю, який тип буде у цієї змінної, а тому мені заздалегідь не відомо, які методи або властивості у неї є. Компілятор, будь ласка, пропусти їх усіх і розберися з ними потім, коли у тебе з'явиться реальний об'єкт в період виконання ". Тому виклик Bar компілюється навіть незважаючи на те, що компілятор не знає, що це таке. Згодом при виконанні у самого об'єкта буде запитано, що робити з цим викликом Bar. І саме TryInvokeMember знає, як це обробляти.


Тепер, припустимо, що замість MyDynamicObject ви скористалися об'єктом з Python:


dynamic d = GetPythonObject();
d.bar(“Baz”, 3, d);

Якщо об'єкт є файлом, перерахованим тут, то код теж буде працювати, і його висновок буде багато в чому аналогічним:


def bar(*args):
  print “Method:”, bar.__name__
  for x in args:
    print “Argument:”, x

За лаштунками при кожному використанні динамічного значення компілятор генерує блок коду, який ініціалізує і використовує DLR CallSite. Цей CallSite містить всю інформацію, необхідну для зв'язування в період виконання, у тому числі ім'я методу, додаткові дані, наприклад відбувається дана операція в перевіряється контексті, і відомості про аргументи та їх типах.


Цей код, якби вам довелося самостійно підтримувати його, був би таким же громіздким і потворним, як і код з відображенням, показаний раніше, код ScriptObject або рядки, які містять XML-запити. У цьому вся суть dynamic в C #:


вам більше не потрібно писати подібний код! При використанні ключового слова dynamic ваш код набуває практично нормальний вигляд, аналогічний простому викликом методу, викликом індексатора, оператору начебто +, Приведення або навіть складовим операторам зразок + = або + +. Динамічні значення можна вказувати і в виразах, наприклад if (d) і foreach (var x in d). Також підтримується коротке замикання (short-circuiting), і тоді код виглядає як d & & ShortCircuited або d?? ShortCircuited.


Цінність DLR, що надає загальну інфраструктуру для операцій такого роду, полягає в тому, що вам більше не потрібно мати справу з різними API для кожної динамічної моделі, яку ви хотіли б використовувати в своєму коді, – все зведено в єдиний API. І вам навіть не потрібно використовувати його. Компілятор C # може використовувати його за вас, і це дозволить вам більше часу приділяти своїм реальним завданням: чим менше інфраструктурного коду пише програміст, тим вище продуктивність його праці.


У мові C # немає скорочень для визначення динамічних об'єктів. Ключове слово dynamic в C # – це все, що потрібне для використання динамічних об'єктів. Розглянемо наступний код:


ynamic list = GetDynamicList();
dynamic index1 = GetIndex1();
dynamic index2 = GetIndex2();
string s = list[++index1, index2 + 10].Foo();


Цей код компілюється, і він містить масу динамічних операцій. Спочатку здійснюється динамічне присвоєння початкового значення index1, а потім динамічне додавання з index2. Далі викликається динамічний індексатор для списку. Результат цих операцій викликає член Foo. Нарешті, фінальний результат висловлювання перетворюється в рядок і зберігається в s. В одній тільки рядку цілих п'ять динамічних операцій, кожна з яких діспетчерізуется в період виконання.


Тип періоду компіляції кожної динамічної операції сам є динамічним, і тому "динамічність" переходить від одного обчислення до іншого. Навіть якщо б ви не включали динамічні вирази кілька раз, все одно ви отримали б цілий ряд динамічних операцій. У цій (єдиної) рядку, як і раніше п'ять динамічних операцій:

string s = nonDynamicList[++index1, index2 + 10].Foo();

Оскільки результати двох виразів з індексами динамічні, сам index є таким. І оскільки отримується в результаті індекс динамічний, таким виявляється і виклик Foo. Потім ви повинні перетворити динамічне значення в рядок. Зрозуміло, це теж виконується динамічно, так як об'єкт може бути динамічним, якому потрібно виконати якісь специфічні обчислення перед запитом на перетворення.


Зверніть увагу в попередніх прикладах на те, що C # дозволяє неявні перетворення з будь-яких динамічних виразів в будь-який тип. Перетворення в рядок в кінці відбувається неявно і не вимагає явної операції приведення типів. Аналогічним чином будь-який тип можна неявно перетворити на динамічний.


У цьому відношенні dynamic сильно нагадує object, і на цьому їх схожість не вичерпується. Коли компілятор генерує вашу збірку і повинен згенерувати динамічну змінну, він використовує тип object, а потім спеціально позначає його. У певному сенсі dynamic є свого роду псевдонімом object, але це додає додаткове поведінка динамічного дозволу операцій при їх використанні.


Ви можете спостерігати це, спробувавши виконати перетворення між узагальненими типами, які розрізняються лише в dynamic і object; такі перетворення завжди спрацьовують, тому що в період виконання примірник List <dynamic> насправді є екземпляром List <object>:

List<dynamic> ld = new List<object>();

Крім того, ви можете помітити подібність між dynamic і object, якщо спробуєте перевизначити який-небудь метод, оголошений з об'єктним параметром:

class C {
public override bool Equals(dynamic obj) {
/* … */
}
}

Хоча в збірці це призводить до вирішення в доповнений об'єкт (decorated object), я віддаю перевагу розглядати dynamic як реальний тип, оскільки він служить свого роду пам'яткою щодо того, що ви можете робити з ним майже все те ж, що і з будь-яким іншим типом. Ви можете використовувати його як аргумент типу або, скажімо, як повертається значення. Наприклад, таке визначення функції дозволить вам використовувати результат виклику цієї функції динамічно без попереднього запису повертається в динамічну змінну:

public dynamic GetDynamicThing() {
/* … */ }

В інтерпретації та диспетчеризації dynamic куди більше деталей, ніж я встиг згадати тут, але вам нема чого знати про них, щоб просто використовувати цю функціональність. Найголовніше в тому, що ви можете писати код, схожий на C #, а якщо якась з його частин є динамічною, компілятор залишить її у спокої до періоду виконання.


І останнє питання, яке відноситься до dynamic і який я хотів би розглянути в цій статті, – обробка помилок. Так як компілятор не перевіряє у динамічній сутності, чи дійсно у неї є викликається вами метод Foo, він не в змозі повідомити вам про будь-яку помилку. Звичайно, це не означає, що ваш виклик Foo не спрацює в період виконання. Він може спрацювати, але існує маса об'єктів, у яких немає ніякого методу Foo. Коли ваш вислів не вдається зв'язати в період виконання, механізм скріплення (binder) в такому випадку робить максимум зусиль згенерувати для вас виняток, який більш-менш точно відображає те, що компілятор сказав би вам у відсутність ключового слова dynamic.


Візьмемо наступний код:

try
{
dynamic d = “this is a string”;
d.Foo();
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
{
Console.WriteLine(e.Message);
}

Тут у мене є рядок, а у рядків точно немає методу Foo. При виконанні коду, що викликає Foo, зв'язування завершиться невдачею, і ви отримаєте виняток RuntimeBinderException. Це і виводить попередня програма:

string does not contain a definition for Foo

Тобто ви отримуєте таке повідомлення про помилку, яке і слід було б очікувати програмісту на C #.


Іменовані аргументи і необов'язкові параметри


Завдяки ще одному додатком для C # методи тепер підтримують необов'язкові параметри зі значеннями за замовчуванням, тому при виклику такого методу ви можете опускати подібні параметри. Переглянути на це у дії можна в наступному класі Car:

class Car {
public void Accelerate(
double speed, int? gear = null,
bool inReverse = false) {

/* … */
}
}


Ви можете викликати метод так:

Car myCar = new Car();
myCar.Accelerate(55);

Це дасть такий самий ефект, що і таке:

myCar.Accelerate(55, null, false);

Ефект однаковий, тому що компілятор вставить всі пропущені вами значення за замовчуванням.


Крім того, C # 4.0 дозволяє викликати методи, вказуючи деякі аргументи на ім'я. Тим самим ви можете передати аргумент на необов'язковий параметр без передачі аргументів для інших параметрів, які передують даному.


Припустимо, ви хочете викликати Accelerate для переключення на зворотну передачу (in reverse), але не бажаєте вказувати параметр gear. Що ж, можна зробити і так:

myCar.Accelerate(55, inReverse: true);

Це новий синтаксис в C # 4.0, і він ідентичний наступного:

myCar.Accelerate(55, null, true);

По суті, є параметри викликається методу необов'язковими чи ні, ви можете використовувати імена при передачі аргументів. Наприклад, ці два виклики припустимі і ідентичні один одному:

Console.WriteLine(format: “{0:f}”, arg0: 6.02214179e23);
Console.WriteLine(arg0: 6.02214179e23, format: “{0:f}”);

Якщо ви викликаєте метод, який приймає довгий список параметрів, такі імена можна використовувати навіть як свого роду внутрікодовую документацію, за якою ви завжди зможете пригадати, який параметр до чого відноситься.


З першого погляду іменовані аргументи і необов'язкові параметри не мають відношення до засобів взаємодії. Ви можете користуватися ними, навіть ніколи не замислюючись про будь-який механізм взаємодії. Однак мотивація розробки такої функціональності криється в Office API. Візьмемо, приміром, програмування Word і що-небудь простеньке начебто методу SaveAs з інтерфейсу Document. У цього методу 16 параметрів, і всі вони необов'язкові. У попередніх версіях C #, якби ви захотіли викликати цей метод, вам довелося б писати приблизно такий код:


Document d = new Document();
object filename = “Foo.docx”;
object missing = Type.Missing;
d.SaveAs (ref filename, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);


А тепер достатньо:

Document d = new Document();
d.SaveAs(FileName: “Foo.docx”);

Я б сказав, що це істотне поліпшення для кожного, хто працює з API, подібними цього. І покращення життя програмістів, які займаються написанням програм Office, безумовно було мотиваційним фактором для додавання до мови іменованих аргументів і необов'язкових властивостей.


Тепер, коли ви пишете. NET-бібліотеку і роздумуєте про додавання методів з необов'язковими параметрами, перед вами постає вибір. Або ви додаєте необов'язкові параметри, або робите так, як роками робили програмісти на C #: вводите перевантажені версії. У прикладі з Car.Accelerate останнє рішення може привести вас до створення типу, який виглядає приблизно так:

class Car {
public void Accelerate(uint speed) {
Accelerate(speed, null, false);
}
public void Accelerate(uint speed, int? gear) {
Accelerate(speed, gear, false);
}
public void Accelerate(uint speed, int? gear,
bool inReverse) {
/* … */
}
}

Вибір моделі, що підходить для створюваної вами бібліотеки, покладається виключно на вас. Оскільки в C # до цих пір не було необов'язкових параметрів, в. NET Framework (у тому числі в. NET Framework 4) спостерігається тенденція до застосування перевантажених версій. Якщо ви вирішите змішати ці підходи і зіставити перевантажені версії з необов'язковими параметрами, механізм вирішення перевантажених версій в C # підпорядковується чітким правилам, визначальним, яка перевантажена версія буде викликатися в конкретних обставинах.


Індексовані властивості


Деякі нові менш значущі мовні засоби в C # 4.0 підтримуються, тільки коли код пишеться в розрахунку на COM Interop API. Один із прикладів – взаємодія з Word в попередніх фрагментах коду.


У коді на C # завжди використовувалася нотація індексатора, який ви можете додати до якогось класу для ефективної перевантаження оператора [] в примірниках цього класу. Ця форма індексатора також називається індексаторів за замовчуванням, оскільки йому не присвоюється ім'я і його можна викликати, не вказуючи якесь ім'я. Проте в деяких COM API є індексатори, відмінні від таких за замовчуванням, які не можна викликати простим використанням [] – ви повинні вказувати конкретне ім'я. В якості альтернативи ви можете вважати, що індексуються властивість – це таке властивість, яка приймає деякі додаткові аргументи.


C # 4.0 підтримує індексовані властивості в типах COM Interop. Визначати типи з індексованими властивостями в C # можна, але можна використовувати їх стосовно до будь-COM-типу. Як виглядає код на C #, здатний робити це, ми розглянемо на прикладі властивості Range робочого листа Excel:

using Microsoft.Office.Interop.Excel;

class Program {
static void Main(string[] args) {
Application excel = new Application();
excel.Visible = true;

Worksheet ws =
excel.Workbooks.Add().Worksheets[“Sheet1”];
// Range is an indexed property
ws.Range[“A1”, “C3”].Value = 123;
System.Console.ReadLine();
excel.Quit();
}
}


У цьому прикладі Range ["A1", "C3"] є властивістю Range, яке повертає щось, що можна індексувати. Це один виклик аксессор Range, в якому передаються A1 і C3. І хоча Value не виглядає індексовані властивістю, насправді воно теж є таким! Всі його аргументи необов'язкові, а оскільки це індексовані властивість, ви можете опускати їх. До введення в мову підтримки індексованих властивостей вам довелося б писати приблизно такий виклик:

ws.get_Range(“A1”, “C3”).Value2 = 123;

Тут Value2 – властивість, доданий просто тому, що індексуються властивість Value не спрацювало б у C # до версії 4.0.


Пропуск ключового слова Ref в COM CallSite


Деякі COM API приймають безліч параметрів, що передаються по посиланню, навіть коли конкретна реалізація нічого не записує в них. У пакеті Office найбільш яскравим прикладом може служити Word – всі його COM API роблять це.


Коли ви стикаєтеся з такою бібліотекою і вам потрібно передавати аргументи за посиланням, ви втрачаєте можливість передавати будь-який вираз, яке не є локальною змінної або полем, а це серйозна головний біль. У прикладі з Word SaveAs ви можете побачити це на практиці: вам довелося оголошувати локальні змінні filename і missing тільки для того, щоб викликати метод SaveAs, так як ці параметри потрібно передавати за посиланням:

Document d = new Document();
object filename = “Foo.docx”;
object missing = Type.Missing;
d.SaveAs(ref filename, ref missing, // …

Ймовірно, ви помітили в новому коді на C #, який був розташований нижче, що я більше не оголошував локальну змінну для імені файлу:

d.SaveAs(FileName: “Foo.docx”);

Це стало можливим завдяки новому механізму, який дозволяє опускати ref при роботі з COM Interop. Тепер при виклику методу через COM Interop ви можете передавати будь-який аргумент за значенням, а не за посиланням. У цьому випадку компілятор створює тимчасову локальну змінну за вас і передає її по посиланню, якщо це потрібно. Звичайно, ви не зможете побачити результат такого виклику методу, якщо цей метод змінює аргумент, тоді вам доведеться передавати аргумент за посиланням.


Все це має зробити код, що використовує подібні API, набагато більш чітким.


Вбудовування типів COM Interop


Це більшою мірою механізм компілятора C #, ніж мовний засіб C #, але тепер ви можете використовувати будь-яку збірку COM Interop без її присутності в період виконання. Мета – зменшити тягар розгортання збірок COM Interop разом з вашим додатком.


Вводячи COM Interop у вихідну версію. NET Framework, розробили концепцію Primary Interop Assembly (PIA). Це було спробою вирішити проблему спільного використання COM-об'єктів різними компонентами. Якщо у вас були різні interop-збірки, які визначали Excel Worksheet, ви не могли розділяти ці Worksheet між компонентами, так як всі вони були різних. NET-типів. PIA усувала цю проблему – її використовували всі клієнти, і. NET-типи завжди збігалися.


Хоча ідея була відмінною на папері, на практиці розгортання PIA перетворилося у велику проблему, тому що безліч додатків намагалося встановити або видалити цю збірку. Справа ускладнювалася тим, що PIA найчастіше були досить великими, Office не встановлював їх за замовчуванням, а користувачі могли легко обійти цю систему єдиної зборки, просто задіявши утиліту TLBIMP і створивши власну interop-складання.


Отже, тепер для виправлення цієї ситуації робиться наступне:



Я змушений опустити ряд деталей через брак місця, але, навіть не знаючи всіх деталей, ви зможете використовувати цей механізм без всяких проблем – повна аналогія з dynamic. Ви повідомляєте компілятору вбудовувати interop-типи за вас і для цього в Visual Studio встановлюєте властивість Embed Interop Types для свого заслання в True.


Оскільки група C # вважає це кращим способом додавання посилань на COM-збірки, Visual Studio встановить цю властивість в True за замовчуванням для будь-якої нової interop-посилання, що додається до проекту на C #. Якщо ви використовуєте компілятор командного рядка (csc.exe) для складання свого коду, то для вбудовування interop-типів ви повинні посилатися на потрібну interop-збірку, використовуючи ключ / L замість / R.


Кожне з засобів, розглянутих мною в цій статті, могло б спричинити за собою куди більш глибоке обговорення, і всі вони заслуговують окремих статей. Я опустив або мигцем згадав дуже багато деталей, але, сподіваюся, моя стаття послужить вам непоганою відправною точкою у вивченні C # 4.0. І якщо ви прочитаєте її, сподіваюся, вам сподобаються поліпшення продуктивності і читаності програми, спроектовані спеціально для вас.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*