outcoldman
outcoldman Denis Gladkikh

Новые возможности .NET 4.0: C# 4.0

.NET, C#, .NET 4.0, C# 4, and Dynamic

После выхода Visual Studio 2010 beta 1 - первым делом нужно разобраться, что же дает нового нам C# 4.0 (так как это мой основной язык программирования - для меня это является важным). Первым делом должен вам порекомендовать примеры C# 4.0, которые можно скачать отсюда (там же есть документ New Features in C# 4.0, которая послужила основой для этого топика). Документацию по .Net Framework 4.0 beta 1 можно посмотреть в MSDN. Дальше будут следовать мой небольшой опыт знакомства с новой версией .NET.

1. Dynamic Language Runtime

Изначально стоит взглянуть на следующую схему, иллюстрирующую архитектуру DLR: 

Именно! Теперь в .net можно еще и скриптовые языки использовать, такие как IronRuby и IronPython. Не думаю, что я буду этим пользоваться, но любителям экзотики предоставляю ссылки:

Более того, предоставляется исходники DLR, при помощи которых вы, наверняка, сможете создать свой динамический язык для .NET, если вам это необходимо

Итак DLR включает в себя Expression Trees, которые просто являются представлением вызовов методов или бинарных операций в виде дерева, их функциональность можно посмотреть на следующем примере:

Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
               param.Name, left.Name, operation.NodeType, right.Value);

В этом примере мы сначала описываем лямбда выражение x=>x<5, а затем при помощи объектов от Expression Trees разбираем данное выражение.

Call Site caching в DLR - это, насколько я понимаю, и есть динамическое представление вызовов методов динамических объектов или операций над динамическим объектами. DLR кеширует характеристики объектов (о типах объектах), а так же об операции, и если данная операция уже была выполнена ранее, тогда всю необходимую информацию DLR получит уже из кеша (вот как то так).

И последнее в DLR это набор классов, интерфейсов: IDynamicMetaObjectProvider, DynamicMetaObject, DynamicObject и ExpandoObject. Давайте опять посмотрим на примере, как нам это может пригодиться, и зачем нам вообще нужен этот DLR:

class Test1
{
}
 
static void Main(string[] args)
{
  dynamic t = new Test1();
  string str = t.Hello(); // Error 1 
 
  dynamic d = 7.0;
  int i = d; // Error 2
}

На удивление данный код скомпилируется и запустится. Все дело в волшебном слове dynamic, оно нам позволяет вызывать любые по имени свойства или методы, а так же приводить объект к любому типу. Во время Runtime (выполнения кода) вылетят ошибки, Error 1: о том, что метод не найден, Error 2: о том, что double невозможно привести к int. Попробуем их исправить:  для исправления первой ошибки наш класс Test1 отнаследуем от типа System.Dynamic.DynamicObject и перегрузим один из методов, для исправления второй просто явно укажем преобразование типов:

class Test1 : DynamicObject
{
  public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
  {
    if (binder.Name == "Hello")
    {
      result = "Test1 is Dynamic Object!";
      return true;
    }
    return base.TryInvokeMember(binder, args, out result);
  }
}
 
static void Main(string[] args)
{
  dynamic t = new Test1();
  string str = t.Hello(); 
 
  dynamic d = 7.0;
  int i = (int) d;
}

Теперь наш код будет работать. Переменная str получить значение "Test1 is dynamic object!", а i значение 7.

Конечно, необязательно наследоваться от класса DynamicObject, можно отнаследоваться и от интерфейса IDynamicMetaObjectProvider, но тогда нужно будет самому реализовывать метод  DynamicMetaObject GetMetaObject(Expression parameter), и более того реализовывать свой тип, унаследованный от DynamicMetaObject, ну в любом случае варианты есть - так что можно взять на вооружение.

2. Именованные и необязательные параметры в методах

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

class Test1 
{
  public void Method(int a = 0, string b = "Hello", bool c = true)
  {
    Console.WriteLine("{0}, {1}, {2}", a, b, c);
  }
}
 
static void Main(string[] args)
{
  Test1 o = new Test1();
  // Вызовем по как обычно
  o.Method(1, "Hello", true);
  // А можно поменять порядок параметров
  o.Method(b: "hello", c: true, a: 1);
  // Можно вообще ничего не вызывать
  // (установлены значения по умолчанию у всех параметров)
  o.Method();
  // Можно определить только необходимые параметры
  o.Method(1, "Hello");
  // И не обязательно по порядку
  o.Method(c: false);
}

Теперь из- за переименование параметра метода, код может и не скомпилироваться, если кто-то использовал установку значения по имени, так что нужно быть аккуратнее. Я рад дефолтным значениям, и постараюсь не использовать функциональность именованных параметров.

В дополнение хочу сказать, что если все таки будет у класса Test1 метод void Method(int a), тогда при вызове o.Method(1) вызовится именно он, а не метод из примера с дефолтными значениями.

3. Возможности для COM Interop

DLR так же дал новые возможности для COM Interop, теперь  можно COM объекты определять как динамические (точнее они уже являются в большинстве своем динамического типа) и не приводить постоянно получаемые объекты к определенным типам для вызова методов или свойств.

excel.Cells[1, 1].Value = "Hello";
// вместо 
((Excel.Range)excel.Cells[1, 1]).Value2 = "Hello";

Данный пример взят из документа New Futures in C# 4.0 С одной стороны приятно, что теперь не нужно мучаться и находить к какому же типу нужно привести объект, чтобы вызвать его свойство или метод, но с другой стороны теряется IntelliSense.

4. Новое в generic

Теперь обогатился и generic новой функциональностью. Можно теперь у интерфейсов и у делегатов перед определением generic типов писать out и in, зачем это чуть дальше, а сначала рассмотрим пример.

При работе с generic часто хочется сделать что то типа такого:

IList<string> strings = new List<string>();
IList<object> objects = strings;

Но нельзя. Потому, что следом можно написать:

objects[0] = 5;
string s = strings[0];

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

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

IEnumerable<object> objects = strings;

Огромную полезность данная функциональность принесет в работе с linq, там часто возникают проблемы, что возвращаем объекты одного типа, а нужно получить список другого типа (базового).

Итак, как же такое стало возможным. Сначала рассмотрим слово out. Теперь интерфейс IEnumerable<T> объявлен как IEnumerable<out T>, где out обозначает, что тип T может быть использован только для возвращения значений, в другом случае компилятор будет ругаться, ну и более того это дает нам, что интерфейс IEnumerable<A> так же есть и IEnumerable<B>, если у A есть возможность приведения типа к B, если на простом примере, то IEnumerable<string>, есть теперь и IEnumerable<object>. Вот пример:

public interface IEnumerable<out T> : IEnumerable
{
  IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
  bool MoveNext();
  T Current { get; }
}

Есть еще слово in. Его так же можно использовать в описании generic делегатов и интерфейсов. Несет оно такую же смысл как и слово out, только в данному случае описанный тип можно использовать только в передаче параметров, вот пример:

public interface IComparer<in T>
{
  public int Compare(T left, T right);
}

То есть в данном случае, если IComparer<object> может считаться и IComparer<string>, потому как если уж он может сравнивать объекты типа object, то и string тоже может.

Так же, как я уже сказал, слова out и in можно применять и к интерфейсам, так, например:

public delegate TResult Func<in TArg, out TResult>(TArg arg);

Заключение

Так же в .NET 4.0 появилось много новвовведений, таких как  Lazy Initialiation - память под объект выделяется тогда, когда это действительно становится нужно. Появились новые типы, как например, BigInteger - теперь не нужно для лабораторных работ студентам писать свои классы для работы с большими числами ;), SortedSet<T> - класс представляет собой самостоятельное балансированное дерево, которое сохраняет данные в отсортированном порядке после вставки, удаления и поиска. В общем, есть еще что изучать.

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.