.NET

индекс
121,03

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

В статье «Обзор 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
24 декабря 2009, 00:22
20

комментарии (72)

+2
Ordos #
Меня это постоянно смущает. Несколько примеров уже видел о том как можно удобно использовать тип dynamic. (Тут уже предлагали с элементами Dictionary работать как со свойствами) Но везде есть приписка «И это далеко не лучшее решение в данном случае».
Да, dynamic делает код намного более читабельным, облегчает разработку, но производительность… Стоит ли это того?
+3
mace #
Авторы в первую очередь разрабатывали dynamic для интеропа и рефлексии, об этом упоминаеться в статье. Эта иллюстрация — очередной простой пример, который не может полностью показать мощь динамика. Для работы с XML существует множество более удобных инструментов, но, представте себе, что завтра вам придется работать с другим иерархическим форматом, для которого не существует XDocument или LINQ. Вот тогда этот исскуственный пример поможет решить вполне реальную задачу.
+1
SunexDevelopment #
На мой взгляд, современное программирование вообще не парится о производительности…
0
butaji #
в 90% случаев меня более всего интересует именно: «делает код намного более читабельным, облегчает разработку»
–8
RiderSx #
Он уже вышел? А то после питона немного не по-себе от строгой типизации…
+2
mace #
Уже есть Visual Studio 2010 Beta 2, на которой вполне можно писать. Релиз должен состоятся в марте следующего года.
Напомню, кстати, что благодаря DLR в .Net 4.0 будут полноценные динамические языки — IronPython и IronRuby.
–1
RiderSx #
Про вижуал студию новую читал, не знал что там будет новая версия шарпа. Спасибо!
0
awhiler #
на март уже не рассчитывайте:)
май, я думаю
0
outcoldman #
откуда сведения?
+4
awhiler #
weblogs.asp.net/scottgu/archive/2009/12/17/visual-studio-2010-and-net-4-0-update.aspx
если добавить к 22 марта few weeks, я думаю будет как раз май:)
+10
Horse #
Я после с++, при виде не типизированных языков с моста хочу спрыгнуть. Типизация — однозначно хорошая штука.
–1
dotCypress #
C# — типизированный язык.
0
Horse #
Спасибо, я догадался.
0
artyfarty #
C# всё равно типизирован, никуда вам от этого не убежать :)
0
Brazier #
А можно с помощью dynamic вызывать конструкторы для Generic типов?
0
mace #
Динамик — это просто объект, тип которого становится известным только на этапе выполнения. То есть к вам приходит экземпляр определенного класса, и вы можете попробовать обратится к любому его члену. Если такового не существует — будет выброшено исключение. Логично предположить, что если экземпляр уже существует, то вряд ли динамик поможет вам вызывать конструкторы. Вот отличная ссылка, как можно динамически создавать дженерик объекты: geekswithblogs.net/marcel/archive/2007/03/24/109722.aspx
Возможно, я не совсем правильно понял ваш вопрос?
0
Brazier #
Спасибо, вы всё верно поняли.

Я то надеялся что всё таки сделают что-то удобное на языковом уровне.
0
Ordos #
Если вы об этом:
var list = new List<dynamic>();
list.Add(1);
list.Add("Hello!");

Да можно, самому интересно стало, проверил — скомпилилось.
0
Mephistophele #
И нормально отработало?
0
Maxmyd #
А в чем сермяжная правда? List
0
Maxmyd #
Хотел спросить, чем List<object> плох в данном случае?
0
mace #
Может тем, что лист динамиков больше похож на кортеж (tuple) из ФП?
+1
Ordos #
Тут надо понимать, что когда в объявляете переменную класса dynamic, вы фактически объявляете object.
То есть если вы вызовете: list.GetType().FullName, то тип переменной list будет List<object>. Просто когда вы явно объявляете List<dynamic>, то к элементам списка можно применить все прелести dynamic`а. Вот и всё.
+4
arilou_camper #
самое интересное, что то, что в VB и VB.NET ругали почем свет стоит — late binding, в C# 4.0 преподносится, как супер крутая фича.
+2
mace #
Но язык ведь остался статически типизированным. Динамик — фича, которую можно использовать только когда она действительно нужна, в остальном используя преимущества статической типизации. Это альтернатива, а альтернатива — всегда хорошо.
0
arilou_camper #
в vb.net с первой его версии тоже был выбор =)
объявляешь переменную как Object, работает late binding, а объявляешь как конкретный тип — используется статическая типизация.
0
mace #
Откровенно говоря, я на бейсике никогда не писал, так что не знал об этом. И все же возражу: во-первых, в C# late binding имеет более явную форму, а во-вторых добавлена возможность переопределять его поведение с помощью DynamicObject — так, как показано в статье. В VB.Net так можно?
0
arilou_camper #
Честно говоря, насчет переопределения не уверен, что настолько же просто это можно было сделать. В Шарпе отлично это реализовали, согласен на 100%.

Безусловно, dynamic — это шаг вперед. Другое дело, что почему-то никто не признает, что MS многие клевые фишки сначала на Бейсике опробует, а потом уже в более «взрослые» языки вставляет. Многие ли сейчас помнят, что такое QLB (Quick Library, которые использовались в Quick Basic 4.5 и Microsoft Basic) и как оно связано с DLL =)
0
mace #
Я не верю, что вы считаете, что нужно опробовать фичи во взрослых языках, а потом вставлять их в бейсик. :) Просто в шарпе все эти фичи реализовывают на более высоком и серьезном уровне, когда понимают, что их реально не хватает в некоторых ситуациях.
0
ulu #
А еще они иногда клевые фишки на C# пробуют, а потом уже в VB.Net вставляют…
0
oddy #
МС параллельно работает над языками C# и VB.NET и в планах у них как раз унификация возможностей языков. Т.е. код будет одинаково работать что в шарпе, что в бейсике. Как тамошние дядьки говорят — выбор разработчика будет между look and feel шарпа и бейсика.
0
awhiler #
vb.net так можно
в Vs2010 они стали практически равными по фичам
0
mace #
Я имел ввиду старый VB.Net. То что в четвертой версии фреймворка они стали равноправными — это только плюс, ИМХО.
–1
dotCypress #
Вы путаете божий дар с яищницей.
dynamic и late binding — не одно и тоже.

Dim myVariable As Object;
myVariable.SomeMethod();

Попробуйте в вашем бэйсике скомпилить этот код :)

0
dotCypress #
Извините за точку с запятой в конце — привычка :)
+1
awhiler #
компилируется.
vb 2010 поддерживает новые фичи DLR ровно в той же степени что и C#

Мало того еще в vb6 был такой тип, как Variant, который очень похож на новый dynamic. В vb.net его правда убили, но вот теперь он снова возродился.

Вообще, смешно читать все эти нападки на vb

к релизу 2010 комманды, делающие vb и c# были объеденены в одну, соотвественно языки полностью обменялись недостающими фичами. например c# получил option parameters, а vb — multiline lambdas.

Далее все фичи будут добавлятся в языки одновременно.

Так что предпочтения могут быть только на уровне вкуса и привычки.
0
arilou_camper #
Чисто из спортивного интереса: в чем отличие между dynamic и late binding?
Даже исключение, которое выбрасывается в случае ошибки, очень похоже ;-)
0
ulu #
Компилируется и в VB Classic, и в VB.Net любой версии
–7
Horse #
Я не удивлен, что С# обзавелся еще одной сомнительной функцией. Сомнительная она потому, что увеличивает комфорт разработки, но и добавляет тормозов программе. С# определенно заботится о программистах, а не о пользователях.
+6
Marsikus #
Баллмер кричал: «Developers! Developers! Developers!», а не «Users! Users! Users!» ))))))))
0
mace #
Тормозов она добавляет только тогда, когда вместо нее можно воспользоватся статической типизацией — а она дает программисту возможность использовать фичи вроде IntelliSense и находить ошибки во время компиляции. Удобство динамика проявляется тогда, когда вместо простого вызова метода или свойства нужно написать кучу кода с помощью рефлексии или какого-нибуть P/Invoke, тогда как динамик просто автоматически згенерирует ту же кучу кода.
–5
Horse #
Скажу честно — не встречал ситуаций, когда мне была надобна динамика. Во многих случаях плохие программисты будут использовать dynamic, даже если он там не нужен. А хорошие программисты редко садятся за С#.
В итоге будут тормоза.
P.S. Поняв, что делает garbage collector с моими алгоритмами, я полюбил С++.
+3
AndreyTS #
С++ не адекватен большинству бизнес-задач. Разработка просто медленнее и дороже. Я и сам люблю С++, но вести реальную работу на нём мне финансово невыгодно :)

По вашему, хорошие программисты редко сядятся за бизнес-приложения?) Хе-хе.
–1
Horse #
Я и сам использую С# для тех случаев, когда конечное приложение должно отработать и закрыться, или для мини приложений.
С++ медленнее и дороже — не всегда, существует много случаев когда С# фичи тормозят процесс разработки.

+3
AndreyTS #
Это бессмысленная дискуссия без указания границ применения. Для моих работ — это так. Я хорошо понимаю и чуствую «фичи» C# и они меня ни разу не тормозят, относительно С++, а наоборот. Если у вас — по другому, значит либо задачи весьма специфические, либо нет нужного понимания C#.
–1
Horse #
Дискуссия не бессмысленна, ваше в ней участие подтверждает это.

С# неадекватен не для специфических задач, а для всего ряда задач, где требуется тонкая работа с памятью. Пример — компьютерные игры, web сервера, алгоритмические программы.

А, вообще, хороший программист должен владеть и С# и С++.
0
Goodkat #
что такое "алгоритмические программы"?
0
Horse #
Под алгоритмическими программами я имел ввиду программы основной задачей которых является реализация одного либо нескольких нетривиальный алгоритмов.
0
Goodkat #
Я пишу такие на php :)
0
Horse #
на php? Всегда думал, что html, php… самые не алгоритмические языки. Можно подробней о программах, что вы пишите на php?
0
Goodkat #
Html — это язык разметки, а php — нормальный язык программирования, на нём можно реализовать алгоритмы любой сложности.
0
chaliy #
А вы бы не могли уточнить какая такая тонкая работа с памятью нужна на web сервере?
0
Horse #
Да хоть с кеш работать. Хранить сессии…
Много всего.
+1
mace #
А я встречал, причем на вполне реальном проекте. Тогда все пришлось разруливать с помощью рефлексии. Попытаюсь в близжайшем будущем проилюстрировать тот пример в статье для хабра — судя по всему тема интересна читателям.
А что касается плохих/хороших программистов, то, мне кажется, вы слишком обобщаете — там, где плохой программист на шарпе добъется тормозов, плохой плюсер будет иметь кучу проблем с утечками памяти или крешами программы. А вот хорошие программисты есть там и там, просто нужно понимать, когда какой инструмент и когда нужно использовать. Если производительность критична в определенном месте, это место всегда можно переписать с помощью С++ или unsafe mode. Если вы пишете софт, который должен работать в режиме реального времени, наверное шарп вам не подойдет. Ежели ваша цель — создать большую корпоративную web-систему электронного документооборота, то компании-заказчику дешевле поставить два дополнительных сервера, чем оплачивать дополнительных пол года разработки этой системы на С++.
–1
Horse #
Совершенно согласен, что касается с++ и с#, soft и hard.
«А хорошие программисты редко садятся за С#» — имел ввиду, что хорошие программисты скорее сядут за с++, чем за С#, а программистики за C#, и никогда за С++.
С++ программы редко работают на 10% быстрее С# аналогов, но бывают случаи когда прирост производительности превышает 1000%
P. S. Есть два языка программирования на которые лезут все, кому не лень — С# и Delphi.

+2
dekko #
Скорее, хороший программист «сядет» за тот язык, который более подходит задаче.
За C# и Delphi (забыли упомянуть еще php для веба) садятся потому что порог вхождения маленький и можно «программировать» в monkey-style. С++ не располагает к этому. Если напишут среду + framework, которые будут располагать к такому стилю на c++, добавим пиар, то будут и так писать, будут медленные и глючные программы на с++.
Дело в людях, а не в языках. Нет лучших или худших языков в принципе.
+1
Horse #
Мы и не говорим, что есть какой-то язык, который лучше всех остальных.
По поводу выбора языка — согласен.
+3
dotCypress #
Вот если бы имели секс дело с COM, вы бы молились на эту фичу ;)
0
Horse #
Я имел дело с COM и даже с CORBA, и все еще не молюсь на эту фичу.
+1
dotCypress #
[irony]
Да, а еще есть .Net Framework, и он весит очень много, и бедным юзерам приходиться его качать.
[/irony]
+3
oddy #
Кто-то из умных дядек говорил, что языки должны быть прежде всего удобны. И это понятно, ведь производительность систем всё ещё растёт по Муру и ПО едва поспевает за мощностями железа. Языки так быстро не развиваются, как развивается вычислительная мощность аппаратных систем. Так что если где-то будет задействован dynamic вместо пачки классов-обёрток — никому от этого очень плохо не будет.
+2
mdevils #
Языки так быстро не развиваются, как развивается вычислительная мощность аппаратных систем.

Вроде, развивать вычислительную мощность уже некуда дальше (пока), а многопоточность не для всех задач подходит.
0
chaliy #
По крайней мере для серверного софта, можно наращивать количество компов.
0
mdevils #
Не все можно распараллелить. Попробуйте распараллелить подсчет числа Фибоначи.
0
chaliy #
Я про реальные задачи. Все что я сейчас пишу можно распаралелить.
0
mdevils #
Я тоже про реальные и к сожалению не все удается вынести в отдельный поток даже, я уже не говорю об отдельной машине. Например, обработать видео с помощью ffmpeg.
0
chaliy #
Давайте я проиллюстрирую мысль.

а) У меня есть 1000 видео клипов. Есть ферма из 100 машин. Каждая из этих 100 мишин будет обрабатывать по 10ть клипов.

б) У меня есть 10 видео клипов. Есть ферма из 100 машин. Я хочу чтобы 10 машин обрабатывали одно видео. Тут я не готов что-то сказать, так как у меня отсутвует опыт с ffmpeg. Быстрее всего можно покрамсать на кусочки (там же есть точки синхронизации?). Может быть что-то еще. Технически задача выполнима.
0
mdevils #
Проблема в том когда есть один БОЛЬШОЙ клип.
0
mdevils #
Если задачу можно разбить на относительно независимые куски, то ее можно распаралелить. Но далеко не все так можно разбить. Примерно это я пытаюсь донести.
0
riffm #
Сразу вспомнил ролик 2007-го года про C# (.Net) и Ruby railsenvy.com/2007/10/9/ruby-on-rails-vs-net-2
+1
chaliy #
Ролик начался с того что вывод типов обозвали «динамической типизпцией». Так держать.
–1
riffm #
Согласен. Но если в толика заменить слова «3.0» на «4.0» и «var» на «dynamic», то ролик опять актуален

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.