В статье «Обзор C# 4.0» я обсуждал некоторые из новых особенностей четвертой версии языка C#. Так получилось, что я не рассмотрел одно из самых важных нововведений: ключевое слово dynamic.
Попросту говоря, объект с типом dynamic не имеет строгой семантики использования на этапе компиляции. Все члены экземпляра определяются во время выполнения с помощью Dynamic Language Runtime (DLR). Вот простой пример:
Как и следовало ожидать, этот код напечатает «3». Однако, оператор «+» и то, чем же, на самом деле, являются переменные int1 и int2 будет определенно не на этапе компиляции, а во время выполнения. Я продемонстрирую вам другой пример (он выглядит так, как будто не должен компилироваться, но, на самом деле, с этим у него все в порядке):
Вообще-то, такой код не должен был бы компилироваться, так как оператор «+» не определен для пары аргументов типа int и Exception. Но, так как мы используем dynamic, код скомпилируется, а уже на этапе выполнения мы получим исключение:
Как же можно использовать все это? Почему мы не можем использовать настоящие типы вместо dynamic? Возможный сценарий использования нововведения: если на этапе компиляции мы не знаем, какого типа объект или же если мы получили объект из необычного источника, например с помощью рефлексии. Раньше при использовании рефлексии приходилось использовать ухищрения вроде методов Invoke или GetProperty, сейчас же достаточно определить объект как динамический и использовать интуитивно-понятный синтаксис. Та же ситуация возникает, когда используется COM Interop: если для объекта не реализована полная обертка на .NET мы можем использовать dynamic и спокойно использовать его без написания большого количества кода для механизма обертки.
Но самым интересным использованием dynamic является определение собственных динамических типов. Для этого в .NET 4.0 ввели новый базовый тип – DynamicObject. Он позволяет определить, как объект должен вести себя во время выполнения, и каким образом он будет обрабатывать свои свойства и методы. С помощью него становится возможным построить некоторые мощные и полезные инструменты.
Я немного работал с PowerShell, и что меня поразило, так это то, как легко на нем работать с XML. Вот кусок кода на PowerShell, который загружает XML-файл:
Сам файл выглядит примерно так:
Таким образом, переменной $backup будет присвоено значение «1440». Мне понравился такой способ доступа к свойствам $xmlConfig, как будто они всегда были там. В предыдущих версиях C# и .NET такое не представлялось возможным. Однако с использованием DynamicObject мы можем создать объект, который сможет работать точно так же. Начнем же!
Создадим класс, который наследуется от DynamicObject:
Самое важное тут – это переопределить метод TryGetMember. Это способ, которым мы скажем DLR, как динамически определять член класса.
Рассмотрим детальнее параметры: «binder» – то, что мы пытаемся определить, параметр, который говорит нам, что именно DLR пытается вызвать у объекта (например, это может быть имя свойства). Параметр «result» – результат вызова, который нужно возвратить DLR. Метод возвращает «True», если удалось успешно определить член динамического объекта и «False», если нет. В нашем случае мы будем возвращать «False», если XML не содержит вершину, к которой мы пытаемся обратиться. Это, в свою очередь, вызовет в DLR исключение во время выполнения.
Конструктор объекта будет принимать параметром объект типа XmlNode. В нашем случае мы будем загружать XmlDocument и передавать его, так как он наследуется от XmlNode.
Таким образом, мы сможем использовать наш класс примерно так:
Свойства, используемые в dynamicXml – это вершины XML, к которым мы пытаемся получить доступ. Мы можем посмотреть в параметре binder, к какому свойству осуществляется доступ и, в случае необходимости рекурсивно вернуть наш динамический объект, для продолжения. В результате мы получим примерно такой код:
Конечно, он не идеален. Он мог бы обрабатывать коллекции более правильным способом, чем просто возвращать список и поддерживать другие типы, кроме строки. И это далеко не лучшее решение в данном случае, существуют те же XDocument и LINQ, которые являются лучшей альтернативой при работе с XML. Однако, я считаю, что это отличная иллюстрация мощи dynamic, которую принес нам C# 4.0.
Попросту говоря, объект с типом dynamic не имеет строгой семантики использования на этапе компиляции. Все члены экземпляра определяются во время выполнения с помощью Dynamic Language Runtime (DLR). Вот простой пример:
static void Main(string[] args)
{
dynamic int1 = 1;
dynamic int2 = 2;
dynamic result = int1 + int2;
Console.WriteLine(result);
}
* This source code was highlighted with Source Code Highlighter.
Как и следовало ожидать, этот код напечатает «3». Однако, оператор «+» и то, чем же, на самом деле, являются переменные int1 и int2 будет определенно не на этапе компиляции, а во время выполнения. Я продемонстрирую вам другой пример (он выглядит так, как будто не должен компилироваться, но, на самом деле, с этим у него все в порядке):
static void Main(string[] args)
{
dynamic int1 = 1;
dynamic ex1 = new Exception("Oops!");
dynamic result = int1 + ex1;
Console.WriteLine(result);
}
* This source code was highlighted with Source Code Highlighter.
Вообще-то, такой код не должен был бы компилироваться, так как оператор «+» не определен для пары аргументов типа int и Exception. Но, так как мы используем dynamic, код скомпилируется, а уже на этапе выполнения мы получим исключение:
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Operator ‘+’ cannot be applied to operands of type ‘int’ and ‘System.Exception’
Как же можно использовать все это? Почему мы не можем использовать настоящие типы вместо dynamic? Возможный сценарий использования нововведения: если на этапе компиляции мы не знаем, какого типа объект или же если мы получили объект из необычного источника, например с помощью рефлексии. Раньше при использовании рефлексии приходилось использовать ухищрения вроде методов Invoke или GetProperty, сейчас же достаточно определить объект как динамический и использовать интуитивно-понятный синтаксис. Та же ситуация возникает, когда используется COM Interop: если для объекта не реализована полная обертка на .NET мы можем использовать dynamic и спокойно использовать его без написания большого количества кода для механизма обертки.
Но самым интересным использованием dynamic является определение собственных динамических типов. Для этого в .NET 4.0 ввели новый базовый тип – DynamicObject. Он позволяет определить, как объект должен вести себя во время выполнения, и каким образом он будет обрабатывать свои свойства и методы. С помощью него становится возможным построить некоторые мощные и полезные инструменты.
Я немного работал с PowerShell, и что меня поразило, так это то, как легко на нем работать с XML. Вот кусок кода на PowerShell, который загружает XML-файл:
$xmlConfig = New-Object XML
$xmlConfig.Load(”Some XML Path”);
$backup = ($xmlConfig.configuration.appSettings.add | where {$_.key –eq ‘BackupEveryInMinutes’}).value;
* This source code was highlighted with Source Code Highlighter.
Сам файл выглядит примерно так:
<configuration>
<appSettings>
<add key="BackupEveryInMinutes" value="1440" />
</appSettings>
</configuration>
* This source code was highlighted with Source Code Highlighter.
Таким образом, переменной $backup будет присвоено значение «1440». Мне понравился такой способ доступа к свойствам $xmlConfig, как будто они всегда были там. В предыдущих версиях C# и .NET такое не представлялось возможным. Однако с использованием DynamicObject мы можем создать объект, который сможет работать точно так же. Начнем же!
Создадим класс, который наследуется от DynamicObject:
public class DynamicXml : DynamicObject
{
}
* This source code was highlighted with Source Code Highlighter.
Самое важное тут – это переопределить метод TryGetMember. Это способ, которым мы скажем DLR, как динамически определять член класса.
public class DynamicXml : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
}
}
* This source code was highlighted with Source Code Highlighter.
Рассмотрим детальнее параметры: «binder» – то, что мы пытаемся определить, параметр, который говорит нам, что именно DLR пытается вызвать у объекта (например, это может быть имя свойства). Параметр «result» – результат вызова, который нужно возвратить DLR. Метод возвращает «True», если удалось успешно определить член динамического объекта и «False», если нет. В нашем случае мы будем возвращать «False», если XML не содержит вершину, к которой мы пытаемся обратиться. Это, в свою очередь, вызовет в DLR исключение во время выполнения.
Конструктор объекта будет принимать параметром объект типа XmlNode. В нашем случае мы будем загружать XmlDocument и передавать его, так как он наследуется от XmlNode.
Таким образом, мы сможем использовать наш класс примерно так:
XmlDocument document = new XmlDocument();
document.Load(”pathtoxml.xml”);
dynamic dynamicXml = new DynamicXml(document);
string value = dynamicXml.configuration.appSettings.add.value;
* This source code was highlighted with Source Code Highlighter.
Свойства, используемые в dynamicXml – это вершины XML, к которым мы пытаемся получить доступ. Мы можем посмотреть в параметре binder, к какому свойству осуществляется доступ и, в случае необходимости рекурсивно вернуть наш динамический объект, для продолжения. В результате мы получим примерно такой код:
using System.Xml;
using System.Collections.Generic;
using System.Dynamic;
namespace CSharp4
{
public class DynamicXml : DynamicObject
{
private readonly XmlNode _node;
public DynamicXml(XmlNode node)
{
_node = node;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
XmlNodeList nodes = _node.SelectNodes(binder.Name);
if (nodes.Count == 0)
nodes = _node.SelectNodes("@" + binder.Name); //Check and see if it's an attribute.
if (nodes.Count == 0)
return false;
if (nodes.Count == 1)
{
XmlNode node = nodes[0];
result = GetContent(node);
}
else
{
List<dynamic> results = new List<dynamic>();
foreach (XmlNode node in nodes)
{
results.Add(GetContent(node));
}
result = results;
}
return true;
}
private dynamic GetContent(XmlNode node)
{
if (node.NodeType == XmlNodeType.Attribute)
return node.Value;
if (node.HasChildNodes || node.Attributes.Count > 0)
return new DynamicXml(node);
return node.InnerText;
}
public override string ToString()
{
return this;
}
public static implicit operator string(DynamicXml xml)
{
return xml._node.InnerText;
}
}
}
* This source code was highlighted with Source Code Highlighter.
Конечно, он не идеален. Он мог бы обрабатывать коллекции более правильным способом, чем просто возвращать список и поддерживать другие типы, кроме строки. И это далеко не лучшее решение в данном случае, существуют те же XDocument и LINQ, которые являются лучшей альтернативой при работе с XML. Однако, я считаю, что это отличная иллюстрация мощи dynamic, которую принес нам C# 4.0.