Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Базовые технологии платформы .NET.docx
Скачиваний:
11
Добавлен:
18.08.2019
Размер:
423.24 Кб
Скачать

27. Динамическое связывание

Определение значения операции, основывающееся на типе или значении составных частей выражения (аргументов, операндов, получателей) в языке C# обычно происходит на этапе компиляции и именуется статическим связыванием (static binding). Если этот процесс осуществляется при выполнении программы, то он называется динамическим связыванием (dynamic binding). Язык C# поддерживает динамическое связывание, начиная с четвёртой версии. Цель динамического связывания – позволить программам на C# взаимодействовать с динамическими объектами, то есть с объектами, которые не подчиняются обычным правилам системы типов C#. Примерами таких объектов являются:

– объекты динамических языков (IronPython, IronRuby, Jscript);

– «расширяемые» объекты, которые позволяют добавлять новые свойства во время выполнения программы (Internet Explorer DOM);

– объекты COM (например, в объектной модели Microsoft Office).

Для указания на необходимость динамического связывания в языке C# используется особый тип – dynamic. У объекта такого типа можно записать вызов любого метода или свойства, это не влияет на компиляцию.

public class Foo

{

public void Print(string s)

{

Console.WriteLine(s);

}

}

// этот код компилируется,

// но при выполнении третья строка генерирует исключение

dynamic obj = new Foo();

obj.Print("Hello world");

obj.Property = 10; // RuntimeBinderException

Тип dynamic может использоваться при объявлении локальной переменной, формального параметра или возвращаемого значения метода, элемента типа (поля, свойства), сконструированного универсального шаблона. Определено неявное преобразование dynamic и других типов друг в друга.

// метод с dynamic-параметрами и возвращаемым значением

public dynamic Div(dynamic x, dynamic y)

{

return x/y;

}

// неявные преобразования

int i = 7;

dynamic d = i;

int j = d;

В качестве примера использования dynamic рассмотрим класс, реализующий перечисляемый слаботипизированный кортеж:

public class IterableTuple : IEnumerable

{

private readonly List<dynamic> _storage;

public IterableTuple(params dynamic[] args)

{

_storage = new List<dynamic>(args);

}

public static IterableTuple Create(params dynamic[] args)

{

return new IterableTuple(args);

}

public dynamic this[int i]

{

get

{

return _storage[i];

}

}

public IEnumerator GetEnumerator()

{

return _storage.GetEnumerator();

}

}

// пример использования IterableTuple

var tuple = IterableTuple.Create(1.5, 12, "string", 69);

foreach (var item in tuple)

{

Console.WriteLine(item);

}

Компилятор заменяет объявление dynamic на объявление object, поэтому с точки зрения CLR эти типы эквиваленты. Работу с dynamic-объектом компилятор организует, используя класс Microsoft.CSharp.RuntimeBinder.Binder (сборка Microsoft.CSharp.dll). Способ нахождения элементов динамического объекта зависит от его конкретного типа:

1. Обычные объекты .NET – элементы определяются при помощи механизма отражения, работа с элементами происходит при помощи позднего связывания.

2. Объект, реализующий интерфейс IDynamicMetaObjectProvider – сам объект запрашивается о том, содержит ли он заданный элемент. В случае успеха работа с элементом делегируется объекту.

3. COM-объекты – работа происходит через интерфейс IDispatch.

Интерфейс IDynamicMetaObjectProvider позволяет разработчикам создавать типы, обладающие динамическим поведением. Обычно данный интерфейс не реализуется напрямую, а выполняется наследование от класса DynamicObject (интерфейс и класс находятся в пространстве имён System.Dynamic). В качестве примера использования DynamicObject приведём класс, обеспечивающий динамический доступ к атрибутам XML-элемента:

public static class XExtensions

{

public static dynamic DynamicAttributes(this XElement e)

{

return new XWrapper(e);

}

private class XWrapper : DynamicObject

{

private readonly XElement _element;

public XWrapper(XElement e)

{

_element = e;

}

// метод вызывается при попытке прочитать значение свойства

public override bool TryGetMember(GetMemberBinder binder,

out object result)

{

result = _element.Attribute(binder.Name).Value;

return true;

}

// метод вызывается при попытке установить значение свойства

public override bool TrySetMember(SetMemberBinder binder,

object value)

{

_element.SetAttributeValue(binder.Name, value);

return true;

}

}

}

// пример работы с XWrapper

XElement x = XElement.Parse(@"<Label Text=""Hello"" Id=""5""/>");

dynamic da = x.DynamicAttributes();

Console.WriteLine(da.Id); // 5

da.Text = "Foo";

Console.WriteLine(x.ToString()); // <Label Text="Foo" Id="5" />

Класс System.Dynamic.ExpandoObject позволяет при выполнении программы добавлять и удалять элементы своего экземпляра:

public sealed class ExpandoObject : IDynamicMetaObjectProvider,

IDictionary<string, object>,

INotifyPropertyChanged

Благодаря динамической типизации работа с пользовательскими элементами ExpandoObject происходит как работа с обычными элементами объекта. Ниже приведён пример расширения ExpandoObject двумя свойствами и методом (в виде делегата).

dynamic sample = new ExpandoObject();

sample.Caption = "The caption"; // добавляем свойство Caption

sample.Number = 10; // и числовое свойство Number

sample.Increment = (Action) (() => { sample.Number++; });

// работаем с объектом sample

Console.WriteLine(sample.Caption); // The caption

Console.WriteLine(sample.Caption.GetType()); // System.String

sample.Increment();

Console.WriteLine(sample.Number); // 11

Объект ExpandoObject явно реализует IDictionary<string, object>. Это позволяет инспектировать элементы объекта при выполнении программы и при необходимости удалять их.

// этот метод преобразует ExpandoObject в XElement

public static XElement ExpandoToXml(dynamic node, string nodeName)

{

var xmlNode = new XElement(nodeName);

foreach (var property in (IDictionary<string, object>) node)

{

if (property.Value is ExpandoObject)

{

xmlNode.Add(ExpandoToXml(property.Value, property.Key));

}

else if (property.Value.GetType() == typeof (List<dynamic>))

{

foreach (dynamic el in (List<dynamic>) property.Value)

{

xmlNode.Add(ExpandoToXml(el, property.Key));

}

}

else

{

xmlNode.Add(new XElement(property.Key, property.Value));

}

}

return xmlNode;

}

// пример работы с ExpandoToXml()

dynamic contact = new ExpandoObject();

contact.Name = "Patrick Hines";

contact.Phone = "206-555-0144";

contact.Address = new ExpandoObject();

contact.Address.Street = "123 Main St";

contact.Address.City = "Mercer Island";

contact.Address.State = "WA";

contact.Address.Postal = "68402";

dynamic res = ExpandoToXml(contact, "Contact");

Console.WriteLine(res);

// будет выведен следующий XML-фрагмент

// <Contact>

// <Name>Patrick Hines</Name>

// <Phone>206-555-0144</Phone>

// <Address>

// <Street>123 Main St</Street>

// <City>Mercer Island</City>

// <State>WA</State>

// <Postal>68402</Postal>

// </Address>

// </Contact>