Expando-об’єкти в C # 4.0, Різне, Програмування, статті

Завантаження прикладу коду


Велика частина коду, написаного для Microsoft. NET Framework, заснована на статичній типізації, хоча. NET підтримує динамічну типізацію через механізм відображення (reflection). Більш того, в JScript система динамічних типів поверх. NET з’явилася десять років тому; те ж саме відноситься і до Visual Basic. Статична типізація має на увазі, що кожен вираз має відомий тип. Типи і присвоювання перевіряються при компіляції, і більшість можливих помилок типізації завчасно відловлюється.


Добре відоме виключення – спроба приведення типів в період виконання, що іноді викликає динамічну помилку, якщо вихідний тип несумісний з цільовим.


Статична типізація забезпечує високу продуктивність і чіткість коду, але заснована на припущенні, що ви заздалегідь знаєте майже все про своє коді і даних. Сьогодні існує велика потреба в деякому пом’якшення цього обмеження. Вихід за рамки статичної типізації, як правило, має на увазі вибір з трьох різних варіантів: динамічної типізації, динамічних об’єктів і непрямого (або заснованого на відображенні) програмування.


В. NET-програмуванні відображення доступно з часів. NET Framework 1.0 і широко застосовувалося для підтримки спеціальних інфраструктур зразок контейнерів Inversion of Control (IoC). Ці інфраструктури дозволяють Залежно типів в період виконання, тим самим дозволяючи вашому коду працювати з інтерфейсом, не знаючи конкретний тип, що стоїть за об’єктом, і його реальну поведінку. З допомогою. NET-відображення ви можете реалізувати різновиди непрямого програмування (indirect programming), де ваш код “спілкується” з посередником об’єктом, який в свою чергу діспетчерізует виклики фіксованому інтерфейсу. Ви передаєте в вигляді рядка ім’я члена для виклику і отримуєте гнучкість у його читанні з якого-небудь зовнішнього джерела. Інтерфейс цільового об’єкта фіксований і незмінний – за будь-якими викликами, що ініціюються через відображення, завжди стоїть певний загальновідомий інтерфейс.


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


В. NET Framework 4 введено ряд нових засобів, що дозволяють виходити за рамки статичних типів. Я вже розповідав про новий ключовому слові dynamic в травневому номері за 2010 р.. У цій статті буде досліджена підтримка динамічно визначених типів, таких як expando-і динамічних об’єктів. За допомогою динамічних об’єктів можна програмно визначити інтерфейс типу замість того, щоб зчитувати його з визначення, статично зберігається в збірках. Динамічні об’єкти поєднують формальну чіткість статично типізованих об’єктів з гнучкістю динамічних типів.


Випадки застосування динамічних об’єктів


Динамічні об’єкти зовсім не призначені для заміни сильних сторін статичних типів. Статичні типи є зараз і залишаться на доступну для огляду перспективу, і саме вони утворюють фундамент розробки ПЗ. При статичної типізації ви надійно виявляєте помилки типів при компіляції і завдяки цьому отримуєте код вільний від перевірок типів в період виконання, а значить, і швидше працює. Крім того, вимога проходження етапу компіляції змушує розробників та архітекторів ретельніше проектувати ПЗ та визначення відкритих інтерфейсів для взаємодіючих рівнів.


Але бувають ситуації, в яких доводиться програмно оперувати відносно добре структурованими блоками даних. В ідеалі, ви б вважали за краще, щоб ці дані надавалися через об’єкти. Але замість цього ви отримуєте їх як простий потік даних – з мережевого з’єднання або читанням з файлу на диску. У цьому випадку у вас два варіанти: використовувати підхід з непрямим програмуванням і застосовувати спеціально підбирається тип (ad hoc type).


У першому випадку ви користуєтеся узагальненим API, який виступає в ролі проксі і впорядковує за вас запити та операції оновлення. У другому випадку ви обзаводитеся специфічним типом, коректно моделюючим дані, з якими ви маєте справу. Питання в тому, хто буде створювати такий тип?


У деяких сегментах. NET Framework вже є хороші приклади внутрішніх модулів, де створюються спеціальні типи для конкретних блоків даних. Один з найбільш очевидних прикладів – ASP.NET Web Forms. Коли ви видаєте запит до ASPX-ресурсу, веб-сервер витягує вміст ASPX-файлу на серверній стороні. Потім цей вміст завантажується в рядок, який обробляється і поміщається в HTML-відповідь. Тому ви отримуєте відносно добре структурований блок тексту, з яким ви потім працюєте.


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


Через цих додаткових витрат група ASP.NET вирішила ввести разову стадію розбору розмітки в клас, який можна компілювати динамічно. Підсумок полягає в тому, що такий простий блок розмітки використовується через спеціальний клас, похідний від класу у відокремленому коді для сторінки Web Forms:

<html>
<head runat=”server”>
<title></title>
</head>
<body>
<form runat=”server”>
<asp:TextBox runat=”server” />
<asp:Button runat=”server” Text=”Click” />
<hr />
<asp:Label runat=”server”></asp:Label>
</form>
</body>
</html>

На рис.1. показана структура класу періоду виконання, створюваного на основі розмітки. Імена методів, позначені сірим кольором, відносяться до внутрішніх процедур, що застосовуються при розборі елементів у екземпляри серверних елементів управління.



Expando-об’єкти в C # 4.0


Рис. 1.Структура динамічно створюваного класу в Web Forms


Ви можете застосувати цей підхід практично в будь-якій ситуації, де ваш додаток багаторазово приймає зовнішні дані для обробки, наприклад потік XML-даних. Для маніпуляцій над XML-даними існує кілька API – від XML DOM до LINQ-to-XML. У будь-якому випадку ви повинні або діяти неявно, запитуючи XML DOM або LINQ-to-XML API, або використовувати ті ж API для самостійного розбору вихідних даних у спеціальні об’єкти.


В. NET Framework 4 динамічні об’єкти пропонують альтернативний, більш простий API для динамічного створення типів на основі деяких вихідних даних. Як приклад навскидку розглянемо таку XML-рядок:

<Persons>
<Person>
<FirstName> Dino </FirstName>
<LastName> Esposito </LastName>
</Person>
<Person>
<FirstName> John </FirstName>
<LastName> Smith </LastName>
</Person>
</Persons>

Щоб перетворити її в програмований тип, в. NET Framework 3.5 ви, ймовірно, написали б приблизно такий код, як нарис.2..


Рис. 2.Застосування LINQ-to-XML для завантаження даних в об’єкт Person

var persons = GetPersonsFromXml(file);
foreach(var p in persons)
Console.WriteLine(p.GetFullName());

// Load XML data and copy into a list object
var doc = XDocument.Load(@”….sample.xml”);
public static IList<Person> GetPersonsFromXml(String file) {
var persons = new List<Person>();

var doc = XDocument.Load(file);
var nodes = from node in doc.Root.Descendants(“Person”)
select node;

foreach (var n in nodes) {
var person = new Person();
foreach (var child in n.Descendants()) {
if (child.Name == “FirstName”)
person.FirstName = child.Value.Trim();
else
if (child.Name == “LastName”)
person.LastName = child.Value.Trim();
}
persons.Add(person);
}

return persons;
}


Цей код використовує LINQ-to-XML для завантаження вихідного контенту в екземпляр класу Person:

public class Person {
public String FirstName { get; set; }
public String LastName { get; set; }
public String GetFullName() {
return String.Format(“{0}, {1}”, LastName, FirstName);
}
}

. NET Framework 4 пропонує інший API для тих же цілей. Цей API, в основі якого лежить новий клас ExpandoObject, більш прямолінійний у використанні і не вимагає від вас планувати, писати, налагоджувати, тестувати і підтримувати клас Person. Давайте докладніше розглянемо ExpandoObject.



Expando-об’єкти в C # 4.0


Використання класу ExpandoObject


Expando-об’єкти були винайдені ще за кілька років до появи. NET. Вперше я почув цей термін, коли описували JScript-об’єкти в середині 1990-х. Expando – це свого роду “надувний” об’єкт, чия структура повністю визначається в період виконання. В. NET Framework 4 його використовують так, ніби це класичний керований об’єкт з тим винятком, що його структура не зчитується з жодної збірки, а формується повністю динамічно.


Expando-об’єкт ідеальний для моделювання динамічно змінюваною інформації, наприклад вмісту конфігураційного файлу. Подивимося, як використовувати клас ExpandoObject для зберігання вмісту з раніше згаданого XML-документа. Повний вихідний код зображений нарис. 3.


Рис. 3.Застосування LINQ-to-XML для завантаження даних в expando-об’єкт

public static IList<dynamic> GetExpandoFromXml(String file) {
var persons = new List<dynamic>();

var doc = XDocument.Load(file);
var nodes = from node in doc.Root.Descendants(“Person”)
select node;
foreach (var n in nodes) {
dynamic person = new ExpandoObject();
foreach (var child in n.Descendants()) {
var p = person as IDictionary<String, object>);
p[child.Name] = child.Value.Trim();
}

persons.Add(person);
}

return persons;
}


Функція повертає список динамічно певних об’єктів. За допомогою LINQ-to-XML ви розбираєте вузли в розмітці і створюєте екземпляр ExpandoObject для кожного з них. Ім’я кожного вузла нижче стає новою властивістю expando-об’єкта, а значенням цієї властивості – текст усередині вузла. Виходячи з даного XML-контенту, ви в результаті отримуєте expando-об’єкт з властивістю FirstName, значення якого одно “Dino”.


Однак нарис. 3 ви бачите, що для заповнення expando-об’єкта застосовується синтаксис індексатора. Це вимагає додаткових пояснень.


Усередині класу ExpandoObject


Клас ExpandoObject належить простору імен System.Dynamic і визначалась в збірці System.Core. ExpandoObject представляє об’єкт, члени якого можна додавати і видаляти динамічно в період виконання. Клас “запечатаний” (sealed) і реалізує ряд інтерфейсів:

public sealed class ExpandoObject :
IDynamicMetaObjectProvider,
IDictionary<string, object>,
ICollection<KeyValuePair<string, object>>,
IEnumerable<KeyValuePair<string, object>>,
IEnumerable,
INotifyPropertyChanged;

Як бачите, клас надає свій вміст, використовуючи різні перераховуються інтерфейси, в тому числі IDictionary і IEnumerable. Крім того, він реалізує IDynamicMetaObjectProvider. Це стандартний інтерфейс, що забезпечує спільне використання об’єкта в Dynamic Language Runtime (DLR) програмами, написаними відповідно до DLR-моделлю взаємодії. Інакше кажучи, тільки об’єкти, реалізують інтерфейс IDynamicMetaObjectProvider, можуть спільно використовуватися динамічними. NET-мовами. Expando-об’єкт можна передати, скажімо, компоненту, написаному на IronRuby. Це не так-то легко зробити у разі звичайного керованого. NET-об’єкта. Вірніше, зробити-то можна, але ви просто не отримаєте динамічної поведінки.


Клас ExpandoObject також реалізує інтерфейс INotifyPropertyChanged. Це дозволяє класу генерувати подія PropertyChanged, коли додається або модифікується будь-який член. Підтримка інтерфейсу INotifyPropertyChanged є ключовою у використанні expando-об’єктів в клієнтських інтерфейсах програм Silverlight і Windows Presentation Foundation.


Ви створюєте екземпляр ExpandoObject так само, як і будь-який інший. NET-об’єкт, але змінну для зберігання примірника оголошуєте з ключовим словом dynamic:

dynamic expando = new ExpandoObject();

На цьому етапі, щоб додати властивість до expando, ви просто привласнюєте йому нового значення:

expando.FirstName = “Dino”;

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

var expando = new ExpandoObject();

Цей код компілюється і працює нормально. Однак при такому визначенні привласнити яке-небудь значення властивості FirstName можна. Клас ExpandoObject – в тому вигляді, як він визначений в System.Core, – не має такого члена. Точніше, у класу ExpandoObject немає відкритих членів.


І це ключовий момент. Коли статичний тип expando є динамічним, операції пов’язуються як динамічні, у тому числі операція перегляду членів. Коли статичним типом є ExpandoObject, операції пов’язуються як звичайні з переглядом членів при компіляції. Таким чином, компілятору відомо, що dynamic – спеціальний тип, але він не знає, що ExpandoObject – спеціальний тип.


Нарис. 4 показані варіанти, запропоновані IntelliSense в Visual Studio 2010, коли expando-об’єкт оголошується як динамічний тип і коли він інтерпретується як простий. NET-об’єкт. В останньому випадку IntelliSense показує вихідні члени System.Object плюс список методів розширення для класів-наборів.



Expando-об’єкти в C # 4.0


Рис. 4.Visual Studio 2010 IntelliSense і expando-об’єкти


Також варто відзначити, що деякі комерційні інструменти в певних обставинах виходять за рамки цього базового поведінки.На рис. 5 показаний ReSharper 5.0, який захоплює список членів, визначених в об’єкті на даний момент. Цього не відбувається, якщо члени додаються програмно через індексатор.


Рис. 5.ReSharper 5.0 IntelliSense в роботі з expando-об’єктами


Щоб додати метод в expando-об’єкт, ви просто визначаєте його як властивість, але використовуєте делегат Action або Func для вираження його поведінки. Ось приклад:

person.GetFullName = (Func<String>)(() => {
return String.Format(“{0}, {1}”,
person.LastName, person.FirstName);
});

Метод GetFullName повертає String, отриманий комбінацією значень властивостей LastName і FirstName, які, як передбачається, доступні в expando-об’єкті. Якщо ви спробуєте звернутися до неіснуючого члену в expando-об’єкті, то отримаєте виняток RuntimeBinderException.


Програми, керовані XML


Щоб зв’язати воєдино концепції, про які я вам розповів, дозвольте мені продемонструвати вам один приклад, де структура даних і структура UI визначаються в XML-файлі. Вміст файлу розбирається в набір expando-об’єктів і обробляється додатком. Проте програма працює тільки з динамічно представленої інформацією і не прив’язане до якогось статичного типу.


У коді нарис. 3 визначається список динамічно визначаються expando-об’єктів person. Як і слід було очікувати, якщо додати новий вузол в XML-схему, в expando-об’єкті буде створено нову властивість. Якщо вам потрібно вважати ім’я члена із зовнішнього джерела, ви повинні застосовувати API індексатора його в ваш expando. Клас ExpandoObject реалізує інтерфейс IDictionary явним чином. Це означає, що вам потрібно відокремити інтерфейс ExpandoObject від типу словника, щоб використовувати API індексатора або метод Add:

(person as IDictionary<String, Object>)[child.Name] = child.Value;

Зважаючи такої поведінки просто редагуйте XML-файл, щоб робити доступними різні набори даних. Але як використовувати це динамічна зміна даних? Ваш UI повинен бути достатньо гнучким, щоб приймати змінюваний набір даних.


Розглянемо простий приклад, де ви лише показуєте дані через консоль. Припустимо, XML-файл містить розділ, що описує очікуваний UI – що б це не означало у вашому контексті. Для цього прикладу я використовую наступне:

<Settings>
<Output Format=”{0}, {1}”
Params=”LastName,FirstName” />
</Settings>

Ця інформація буде завантажуватися в іншій expando-об’єкт за допомогою такого коду:

dynamic settings = new ExpandoObject();
settings.Format =
node.Attribute(“Format”).Value;
settings.Parameters =
node.Attribute(“Params”).Value;

В основної процедури буде наступна структура:

public static void Run(String file) {
dynamic settings = GetExpandoSettings(file);
dynamic persons = GetExpandoFromXml(file);
foreach (var p in persons) {
var memberNames =
(settings.Parameters as String).
Split(,);
var realValues =
GetValuesFromExpandoObject(p,
memberNames);
Console.WriteLine(settings.Format,
realValues);
}
}

Expando-об’єкт містить формат виводу, а також імена членів, значення яких будуть відображатися. Стосовно до динамічного об’єкту person вам потрібно завантажити значення для зазначених членів, використовуючи приблизно такий код:

public static Object[] GetValuesFromExpandoObject(
IDictionary<String, Object> person,
String[] memberNames) {

var realValues = new List<Object>();
foreach (var m in memberNames)
realValues.Add(person[m]);
return realValues.ToArray();
}


Оскільки expando-об’єкт реалізує IDictionary , ви можете використовувати API індексатора для зчитування та завдання значень.


Нарешті, список значень, витягнутих з expando-об’єкта, передається в консоль для відображення.На рис. 6 представлені два знімки консольного застосування, вся різниця між якими визначається структурою нижележащего XML-файла.



Expando-об’єкти в C # 4.0


Рис. 6.Два приклади консольного застосування, керованого XML-файлом


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

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


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

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

Ваш отзыв

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

*

*