Tuesday, January 21, 2014

Динамические возможности языка C#

Языки разработки c динамической типизацией, как Python, JavaScript, PHP и другие, мне импонируют; в частности, одна из статей посвящена возможностям языка Python в C# ("ИспользованиеIronPython в C#"). Но в данной статье речь пойдет о динамических возможностях именно языка C#. В C# динамическая типизация представлена с помощью типа dynamic и классов DynamicObject и ExpandoObject. Отличие DynamicObject от ExpandoObject небольшое, и оно зависит исключительно от объема проделанной работы. Для DynamicObject ее нужно проделать больше, так как нужно переопределить методы TryGetMember, TrySetMember и TryInvokeMember. Но поскольку человек - существо довольно ленивое, пойдем от простого сценария и рассмотрим динамические возможности языка на примере ExpandoObject.
static void Main(string[] args)
{
    dynamic expandoObject = new ExpandoObject();
    expandoObject.FirstName = "Aleksandr";
    expandoObject.LastName = "Polukhovich";
    expandoObject.FullName = expandoObject.FirstName + " " + expandoObject.LastName;
    expandoObject.MethodResult = (Func<int, int>)(i => i*i);
    expandoObject.PrintToConsole = (Action)(() => Console.WriteLine("Test"));
    expandoObject.PrintWithParam = (Action<string>)(Console.WriteLine);

    Console.WriteLine(expandoObject.FirstName);
    Console.WriteLine(expandoObject.LastName);
    Console.WriteLine(expandoObject.FullName);
    expandoObject.PrintToConsole();
    Console.WriteLine(expandoObject.MethodResult(3));
    expandoObject.PrintWithParam("Test ExpandoObject");
    Console.ReadLine();
}
Выше приведён пример для создания динамического объекта и демонстрирующий, как можно задать необходимые свойства на лету. Добавление свойств осуществляется легко, быстро и в каком угодно количестве.
expandoObject.FirstName = "Aleksandr";
expandoObject.LastName = "Polukhovich";
expandoObject.FullName = expandoObject.FirstName + " " + expandoObject.LastName;
Добавление новых функций также выполняется достаточно просто.
expandoObject.MethodResult = (Func<int, int>)(i => i*i);
expandoObject.PrintToConsole = (Action)(() => Console.WriteLine("Test"));
expandoObject.PrintWithParam = (Action<string>)(Console.WriteLine);
Мы можем также простые функции добавлять к нашему объекту.
expandoObject.HelloFunction = Test();
Console.WriteLine(expandoObject.HelloFunction);
Для более сложных функций необходимо приведение к нужному типу делегата.
public static int Test(int i, int j, int v)
{
    return i + j + v;
}
Использование данной функции будет иметь следующий вид:
dynamic testFunction = new ExpandoObject();
testFunction.Sum = (Func<int, int, int, int>) Test;
Console.WriteLine(testFunction.Sum(1,1,1));
Одно из преимуществ использования динамических типов в том, что IntelliSense в Visual Studio 2012 уже хоть немного начал работать в пределах одного метода. Раньше с этим было проблематично.













На заметку: если Вы планиуете использовать в своем проекте динамические возможности языка C#, то стоит помнить о нескольких особенностях:
  • Отсутствие поддержки IntelliSense.
  • Динамическая типизация работает медленно, так как работает через рефлексию.
  • Очень велика вероятность допустить ошибку при разработке.
Рассмотрим, как этот же пример будет выглядеть, если вместо ExpandoObject использовать DynamicObject.
public class ElasticObject : DynamicObject
{
    private Dictionary<object, object> _results = new Dictionary<object, object>();
           
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (_results.ContainsKey(binder.Name))
        {
            result = _results[binder.Name];
            return true;
        }

        result = "Property not found";
        return false;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var del = (Delegate) _results[binder.Name];
        result = del.DynamicInvoke(args);

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (!_results.ContainsKey(binder.Name))
            _results.Add(binder.Name, value);
        else
            _results[binder.Name] = value;
        return true;
    }
}
Использование нашего класса ElasticObject:
static void Main(string[] args)
{
    dynamic expandoObject = new ElasticObject();
    expandoObject.FirstName = "Aleksandr";
    expandoObject.LastName = "Polukhovich";
    expandoObject.FullName = expandoObject.FirstName + " " + expandoObject.LastName;
    expandoObject.MethodResult = (Func<int, int>)(i => i * i);
    expandoObject.PrintToConsole = (Action)(() => Console.WriteLine("Test"));
    expandoObject.PrintWithParam = (Action<string>)(Console.WriteLine);


    Console.WriteLine(expandoObject.FirstName);
    Console.WriteLine(expandoObject.LastName);
    Console.WriteLine(expandoObject.FullName);
    expandoObject.PrintToConsole();
    Console.WriteLine(expandoObject.MethodResult(3));
    expandoObject.PrintWithParam("Test ExpandoObject");

    Console.ReadLine();
}

Использование динамических возможностей в C# - очень мощная вещь, но за это мы платим производительностью. Поэтому задумайтесь об использовании этих возможностей в своей программе, возможно, есть другое решение, которое не подразумевает использование динамических типов данных. Надеюсь, что данное краткое руководство поможет Вам в использовании динамических свойств языка С# и сделает Ваше программное обеспечение более гибким.

No comments:

Post a Comment