пятница, 27 января 2012 г.

Что такое dynamic, и с чем его едят.

Сегодня я хотел бы затронуть тему динамических типов в C# и ключевого слова dynamic.
Как известно, в .NET 3.0, C# обзавелся ключевым словом var, которое позволяет объявить локальную, неявно типизированную переменную.
var s = "String"; //"s" имеет тип string
Также переменную можно объявить как object. Ни для кого не секрет, что System.Object возглавляет иерархию классов .NET Framework и переменная объявленная как object, может представлять все что угодно. Таким образом мы получим следующее:
object o = new Employee() {Name = "Employee1"};
//"o" может быть приведена к типу Employee Console.WriteLine("Employee’s name is: {0}", ((Employee)o).Name);
В .NET 4.0, С# приобрел ключевое слово dynamic. Для его использования в проекте необходимо иметь ссылку на пространство имен Microsoft.CSharp.RuntimeBinder.
Dynamic чем-то похож на object. Типу dynamic может быть присвоено любое значение. Наличие трех способов объявления переменных, внутренний тип которых не указан явно, может смутить. Но в отличие от переменной объявленной неявно с помощью var и от переменной объявленной через ссылку с помощью object, динамическая переменная объявленная с помощью dynamic не является строго типизированной. Т.е. переменная объявленная с ключевым словом dynamic может получить какое угодно значение и за все время жизни этой переменной, ее значение может быть заменено, и необязательно новое содержание должно иметь тип связанный с первоначальным значением:
dynamic d = "String";
Console.WriteLine("Type of d is {0}", d.GetType());
d = 15;
Console.WriteLine("Type of d is {0}", d.GetType());
d = true;
Console.WriteLine("Type of d is {0}", d.GetType());
d = new Employee() {Name = "Employee1"};
Console.WriteLine("Type of d is {0}", d.GetType());
Вывод на экран будет следующим:

Type of d is System.String
Type of d is System.Int32
Type of d is System.Boolean
Type of d is Sample.Employee

Результат был бы идентичным, если переменная d была бы объявлена как object. Но dynamic предоставляет ряд дополнительных возможностей, которых нет у System.Object.

Для вызова общедоступных членов у динамической переменной нужно просто применить операцию точки и указать интересующий нас метод (свойство, событие и т.д.) и передать аргументы (если необходимо). Как уже известно, объект типа dynamic обходит проверку статического типа в отличие от объекта типа System.Object. В связи с этим компилятор не может проверить корректность вызываемых методов (свойств, событий...), поддерживает ли их объект неизвестно вплоть до прямого к нему обращения. Следующий код скомпилируется без ошибок, но при запуске возникнет ошибка времени выполнения RuntimeBindingException:
dynamic d = "some data";
Console.WriteLine(d.Length);
d.Foo(false, 10);
d.Count();
Переменная d имеет тип string и мы без проблем получаем длину строки обращаясь к свойству Length. Но у типа string нету метода по имени Foo(), принимающего в качестве аргументов Boolean и int, да и метода Count() у типа string никогда не было. Запустив данный код мы получим следующее сообщение:

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: “string” does not contain a definition for “Foo”

Поэтому используя динамические переменные нужно быть очень внимательным, вызывая их общедоступные члены. Любая ошибка в имени метода, свойства... приведет к RuntimeBinderException. Логично допустить, что вызов членов на динамической переменной желательно помещать в блок try/catch отлавливая RuntimeBinderException.

«Я есмь альфа и омега, начало и конец…»
(Библия: Откровение. 1:8)

Dynamic есмь поле и свойство...
Dynamic может выступать в роли: - типа поля - типа свойства - типа возвращаемого методом значения - типа аргументов, передаваемых методу - типа локальной переменной
public class ClassWithDynamicMembers
{
    private dynamic dynamicField;
    public dynamic DynamicProperty { get; set; }

    public dynamic StrangeDynamicMethod(dynamic dynamicParam1,
dynamic dynamicParam2) { dynamic dynamicLocalVar = "dynamicVar"; if (dynamicParam1 is string) dynamicLocalVar += dynamicParam1; if (dynamicParam2 is Boolean) return dynamicParam2; return false; } }
Но есть одно неприятное ограничение: объект типа dynamic не воспринимает расширяющие методы и как следствие с таким объектом не получится использовать технологию LINQ:
dynamic d = new List() {"a", "b", "c"};
var linqData = from data in d sеlect data;
Применение ключевого слова dynamic
В связи с тем, что во время компиляции предполагается, что объекты с типом dynamic могут поддерживать любые операции, то таким объектам абсолютно все-равно откуда они получат свое значение – из COM, из динамического языка типа IronPython или IronRuby, из объектной модели DOM HTML, посредством рефлексии или из какого-либо другого места программы.
Работа с динамическими языками такими, как IronPython и IronRuby осуществляется благодаря среде DLR (Dynamic Language Runtime), появившейся в .NET 4.0, которая собственно и обеспечивает инфраструктуру, поддерживающую тип dynamic в C#. Описание среды DLR, ее архитектуры и прочего не является целью данной статьи. Но хотелось бы остановиться на dynamic и его упрощенном взаимодействии с API COM.

Взаимодействие с API COM
В прошлых версиях .NET, при разработке приложений на C#, имеющих дело с библиотеками COM, возникал ряд сложностей. Часто COM-библиотеки определяют метода в которых присутствуют необязательные параметры, что требовало указания значения Type.Missing. Сейчас, если необязательные параметры не указаны, то они автоматически получат значение Type.Missing во время компиляции. Еще одним примером проблематичной работы с COM-библиотеками, можно назвать то, что многие методы COM могут принимать в качестве аргументов и возвращать тип данных Variant. Также как и dynamic, объект типа Variant может принимать значения разных типов данных на лету. До .NET 4.0 и появления ключевого слова dynamic в С#, работать с данными типа Variant можно было посредством object, что приводило к многочисленным приведениям типов. С использованием dynamic проблемы в работе с типами Variant как рукой сняло.
Например, для создания пустого Word файла, сохранения его с определенным именем и последующим закрытием Word'а, без использования dynamic, нужно совершить следующие телодвижения:
Word.Application wordApp = new Word.Application() { Visible = true };
object tm = Type.Missing;
object visibility = true;
object fileName = @"C:\COMPreDynamic.doc";
Word.Document word = wordApp.Documents.Add(ref tm, ref tm, ref tm,
ref visibility); word.SaveAs(fileName); word.Close(ref tm, ref tm, ref tm); wordApp.Application.Quit(ref tm, ref tm, ref tm);
При работе с COM, dynamic используется неявно. Все что нужно сделать, это в подключенной COM библиотеке установить свойство Embed Interop Types в True. С этого момента все типы Variant начнут автоматически отображаться на динамические данные. Итак, с учетом вышесказанного, код с dynamic будет выглядеть намного читабельнее и проще:
Word.Application wordApp = new Word.Application() { Visible = true };
Word.Document word = wordApp.Documents.Add(Visible: true);
word.SaveAs(FileName: @"C:\COMWithDynamic.doc");
word.Close();
wordApp.Application.Quit();
Несколько слов в завершение
Польза от динамических типов несомненно есть (как минимум упрощенное взаимодействие с COM). Но даже если применять динамические типы в повседневных задачах (что без особых на то причин, крайне не рекомендуется), то важно помнить, что использование динамических типов приводит к потере безопасности типов и как результат код становится подвержен большему количеству ошибок RuntimeBinderException.

Комментариев нет:

Отправить комментарий