суббота, 30 апреля 2011 г.

Динамические возможности C# 4.0 – тип dynamic

 

В наше время программисты все чаще отдают предпочтение динамическим языкам программирования, таким как JavaScript, Perl, Python и Ruby. Многие из них предпочитают избавиться от излишне сложных и устаревших методик и стремятся использовать подходы, позволяющие упростить и ускорить разработку. C# хоть и остаётся статически типизированным языком, имеет некоторые динамические возможности, благодаря существованию слова var и анонимных методов. В версию .NET 4.0 был добавлен тип dynamic. О нём и пойдёт речь в этой статье.

Dynamic Language Runtime

Динамические возможности C# 4.0 – это часть исполняющей среды динамического языка (Dynamic Language Runtime). Среда DLR представляет собой среду выполнения, которая добавляет набор служб для динамических языков в среду CLR. Среда DLR упрощает разработку динамических языков, используемых в .NET Framework и добавляет динамические функции в языки со статической типизацией. Как и CLR, среда DLR является частью .NET Framework и поставляется в установочных пакетах .NET Framework и Visual Studio. Кроме того, по адресу CodePlex можно загрузить версию с открытым исходным кодом.

Среда DLR обеспечивает следующие преимущества:

· упрощает перенос динамических языков в .NET Framework;

· обеспечивает динамические функции в языках со статической типизацией;

· обеспечивает будущие преимущества среды DLR и .NET Framework;

· обеспечивает общий доступ к библиотекам и объектам;

· обеспечивает быструю динамическую отправку и вызов.

Дополнительные сведения о том, как с помощью версии среды DLR с открытым исходным кодом добавить динамические функции в язык или как обеспечить использование динамического языка в .NET Framework, см. в документации по адресу CodePlex.

Тип dynamic

Тип dynamic позволяет писать код, который обходит проверку времени компиляции. Компилятор предполагает, что любая операция, определенная для объекта типа dynamic, является допустимой. Если операция некорректна, ошибка не будет обнаружена вплоть до времени выполнения.

В отличие от ключевого слова var, объект, объявленный как dynamic, может менять тип во время выполнения. Напомню, что при использовании ключевого слова var определение типа объекта откладывается. Но как только он определен компилятором, изменять его уже нельзя. Что касается объекта dynamic, то можно не просто изменить его тип, но делать это многократно. Это отличается от приведения объекта от одного типа к другому. При приведении объекта создается новый объект с другим, но совместимым типом.

Но как же реализовано внутри описанное выше поведение? C# по-прежнему является статически типизированным языком. Это не изменилось. Давайте взглянем на код IL, который генерируется при использовании типа dynamic.

Сначала возьмем пример кода C#, который выглядит следующим образом:

using System;

namespace MyDynamic

{

static class Program

{

static void Main(string[] args)

{

IpAdress staticIp = new IpAdress(IpType.статический);

IpAdress dynamicIp = new IpAdress(IpType.динамический);

Console.WriteLine(staticIp.StaticIp);

Console.WriteLine(dynamicIp.DynamicIp);

Console.ReadLine();

}

}

class IpAdress

{

private string staticIp;

public string StaticIp

{

get { return staticIp; }

set { staticIp = value; }

}

private dynamic dynamicIp;

public dynamic DynamicIp

{

get { return dynamicIp; }

set { dynamicIp = value; }

}

private IpType ipType;

internal IpType IpType

{

get { return ipType; }

set { ipType = value; }

}

public IpAdress(IpType ipType)

{

if (ipType == IpType.статический)

{

staticIp = "192.168.99.9";

dynamicIp = "0.0.0.0";

}

else

{

staticIp = "0.0.0.0";

dynamicIp = "178.10.0.1";

}

}

}

enum IpType

{

статический,

динамический

}

}

Здесь определён класс — IpAdress, который имеет два поля, одно возвращает string, а другое объект dynamic. Метод Main просто создает эти объекты и печатает значения, возвращаемые методами. Теперь закомментируем ссылки на DynamicIp и посмотрим IL код, сгенерированный для метода Main. Посмотреть это можно с помошью инструмента ildasm.

В диалоговом окне Run меню Start введите команду ildasm и щелкните ОК. Перед вами появится довольно невзрачного вида приложение с несколькими меню. В меню File выберите команду Open. В появившемся диалоговом окне File Open откройте папку с нашим примером и выберите исполняемый файл.

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// Code size 27 (0x1b)

.maxstack 2

.locals init ([0] class MyDynamic.IpAdress staticIp)

IL_0000: nop

IL_0001: ldc.i4.0

IL_0002: newobj instance void MyDynamic.IpAdress::.ctor(valuetype MyDynamic.IpType)

IL_0007: stloc.0

IL_0008: ldloc.0

IL_0009: callvirt instance string MyDynamic.IpAdress::get_StaticIp()

IL_000e: call void [mscorlib]System.Console::WriteLine(string)

IL_0013: nop

IL_0014: call string [mscorlib]System.Console::ReadLine()

IL_0019: pop

IL_001a: ret

} // end of method Program::Main

Посмотрев на этот IL код, можно увидеть, что тут всего лишь вызывается конструктор, идёт обращение к полю StaticIp и выводится на печать. Теперь давайте закомментируем обращение и вывод StaticIp, а остальное раскомментируем и посмотрим теперь на IL код.

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// Code size 126 (0x7e)

.maxstack 8

.locals init ([0] class MyDynamic.IpAdress dynamicIp,

[1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)

IL_0000: nop

IL_0001: ldc.i4.1

IL_0002: newobj instance void MyDynamic.IpAdress::.ctor(valuetype MyDynamic.IpType)

IL_0007: stloc.0

IL_0008: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> MyDynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'

IL_000d: brtrue.s IL_0052

IL_000f: ldc.i4 0x100

IL_0014: ldstr "WriteLine"

IL_0019: ldnull

IL_001a: ldtoken MyDynamic.Program

IL_001f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)

IL_0024: ldc.i4.2

IL_0025: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo

IL_002a: stloc.1

IL_002b: ldloc.1

IL_002c: ldc.i4.0

IL_002d: ldc.i4.s 33

IL_002f: ldnull

IL_0030: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,

string)

IL_0035: stelem.ref

IL_0036: ldloc.1

IL_0037: ldc.i4.1

IL_0038: ldc.i4.0

IL_0039: ldnull

IL_003a: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,

string)

IL_003f: stelem.ref

IL_0040: ldloc.1

IL_0041: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,

string,

class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,

class [mscorlib]System.Type,

class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)

IL_0046: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)

IL_004b: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> MyDynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'

IL_0050: br.s IL_0052

IL_0052: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> MyDynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'

IL_0057: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>>::Target

IL_005c: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> MyDynamic.Program/'<Main>o__SiteContainer0'::'<>p__Site1'

IL_0061: ldtoken [mscorlib]System.Console

IL_0066: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)

IL_006b: ldloc.0

IL_006c: callvirt instance object MyDynamic.IpAdress::get_DynamicIp()

IL_0071: callvirt instance void class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>::Invoke(!0,

!1,

!2)

IL_0076: nop

IL_0077: call string [mscorlib]System.Console::ReadLine()

IL_007c: pop

IL_007d: ret

} // end of method Program::Main

Точно можно сказать, что для поддержки динамического типа компилятор С# выполнил намного больше работы. В сгенерированном коде несложно заметить ссылки на System.Runtime.CompilerServices.CallSite и System.Runtime.CompilerServices. CallSiteBinder.

CallSite представляет собой тип, обрабатывающий поиск во время выполнения.Когда во время выполнения осуществляется вызов на динамическом объекте, кто-то должен просмотреть объект и проверить, существует ли указанный член. CallSite кэширует эту информацию, так что этот поиск не приходится выполнять повторно. Без этого производительность циклических структур была бы под вопросом. После того, как CallSite выполнит поиск члена, вызывается CallSiteBonder. Он извлекает информацию у CallSite и генерирует дерево выражения, представляющее операцию, к которой нужно привязаться. Очевидно, что здесь много чего происходит. Поэтому следовало очень постараться, чтобы оптимизировать эту очень сложную операцию.

Должно быть очевидным, что хотя применение типа dynamic удобно, за это приходится платить свою цену.

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

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