01 ноября 2008

Новые возможности C# 4.0. Часть 1: dynamic

Одна из самых интересных возможностей язык C# 4.0, который был представлен на PDC является новое ключевое слово - dynamic. Оно позволяет разработчику объявить объект, привязка к методам которого, будет осуществлятся на этапе выполнения, а не компиляции. Примечательно, что класс, который инстанциирует этот объект объявляется стандартным способом.

   1:  public class TestClass
   2:  {
   3:      public void TestMethod1()
   4:      {
   5:          Console.WriteLine("test method 1");
   6:      }
   7:   
   8:      public void TestMethod2()
   9:      {
  10:          Console.WriteLine("test method 2");
  11:      }        
  12:  }

Теперь мы можем вызывать его методы как обычно:

   1:  var test = new TestClass();
   2:  test.TestMethod1();
   3:  test.TestMethod2();

Как мы и ожидали все прекрасно скомпилировалось. Теперь воспользуемся новым ключевым словом dynamic:

   1:  dynamic test = new TestClass();
   2:  test.TestMethod1();
   3:  test.TestMethod2();

Ничего не изменилось? Отнюдь. Все точно так же компилируется, как и раньше, но теперь вызовы методов будут привязываться на этапе выполнения, а не компиляции. Тоесть, если мы напишем вот так:

   1:  dynamic test = new TestClass();
   2:  test.TestMethod1();
   3:  test.TestMethod2();
   4:  test.TestMethod3();

Код все-равно успешно скомпилируется, но после запуска мы получим данное исключение:

Что же тут произошло? Вот что нам подсказал Reflector:

   1:  private static void Main(string[] args)
   2:  {
   3:      object test = new TestClass();
   4:      if (<Main>o__SiteContainer0.<>p__Site1 == null)
   5:      {
   6:          <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), false, false, "TestMethod1", typeof(object), null));
   7:      }
   8:      <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, test);
   9:      if (<Main>o__SiteContainer0.<>p__Site2 == null)
  10:      {
  11:          <Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), false, false, "TestMethod2", typeof(object), null));
  12:      }
  13:      <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, test);
  14:  }

Для начала компилятор сгенерировал поле o__SiteContainer0, для хранения информации о месте вызова. Если вы посмотрите на переменную test, которая содержит наш объект, то увидите, что ее тип стал типом object. Так что, в реальности, это не динамический тип, а всего-лишь помощник со стороны компилятора.

Дальше идет проверка мест вызова(callsite) на null и если они ему равны, то используется CallSite.Create, в который передается объект CSharpCallPayload, который содержит всю информацию о методе, который мы вызываем. Как только место вызова указано, то оно просто вызывается у нашей переменной test. Как видите - компилятор все сделал за нас.

В данном примере то что мы делаем кажется чертовски бесполезным, но мощь этой возможности проявляется тогда, когда мы используем тип, методы которого мы не знаем на этапе компиляции. Это может прийти от динамических языков(например IronRuby), если это сгенерированный класс, или что угодно из того, для чего мы используем Reflection.

Ремарка: я принял рещение исключить из перевода часть про тестирование производительности, так как в статье C# 4.0 New Features Part 1.1 - dynamic keyword second look автор провел новое тестирование и оно показало заметно лучшие результаты.

Перевод статьи C# 4.0 New Features Part 1 - dynamic keyword

5 комментариев: