Как стать автором
Обновить

Интересные вещи, которые можно делать с dynamic в .NET 4.0

Время на прочтение6 мин
Количество просмотров5K
Автор оригинала: Kevin Jones
В статье «Обзор C# 4.0» я обсуждал некоторые из новых особенностей четвертой версии языка C#. Так получилось, что я не рассмотрел одно из самых важных нововведений: ключевое слово dynamic.

Попросту говоря, объект с типом 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.
Progg it
Теги:
Хабы:
+13
Комментарии72

Публикации

Истории

Работа

.NET разработчик
74 вакансии

Ближайшие события