Sunday, August 17, 2014

Переход на .NET Framework 4.5. Преимущества и недостатки

Плюсы перехода с .NET Framework 4.0 на .NET Framework 4.5 в для разработчиков WPF
Самом .NET Framework 4.5 претерпел серьезных изменений, по сравнению с 4-й версией фреймворка. Было сделано множество вещей, которые позволяют писать программный код более гибко; много возможностей, которые раньше поставлялись отдельно, теперь поставляются в паре с самим фреймворком. Теперь не нужно таскать с собой разные библиотеки, это позволит избежать одной популярной и неприятной проблемы, которая известна среди разработчиков как DLL hell. Разделение плюшек, которые могут принести новые возможности 4.5 фреймворка, я решил сделать в двух частях. Первая часть – это преимущество с точки зрения конечного пользователя, 2-я часть техническая и будет включать в основном те специфические вещи, которые позволят команде улучшить процесс разработки и написания кода.
Ribbon Control
В .NET Framework 4.0 для  работы с риббонами была доступна бесплатная библиотека RibbonControlsLibrary, которую можно было загрузить с codeplex. Она адаптирована для использования в .NET Framework 4.0, поэтому имеет множество заточенных возможностей, которые были специально адаптированы под конкретный фреймфорк. Риббоны позволяют создавать составные панели и писать софт по аналогии с Microsoft Office. Пример на рисунке ниже.
Начну с нескольких вариантов в пользу использования новых риббон-контролов, которые появились в WPF 4.5 (4.5 фреймворк). Во-первых, это пройдет для вашего проекта безболезненно по той причине, что пространства имен не изменились, контролы не изменились, они просто дополнились и подверглись небольшим изменениям.
Вторая причина замены – это то, что Ribbon-контролы, которые есть в вашем проекте, уже включены в состав самого фреймворка и не нуждаются в отдельных библиотеках. Это минус одна библиотека в поставку.
Третья причина менее значительна, чем две предыдущие, но она также достаточно интересна. Размер библиотеки, адаптированной под 4-й фреймфорк, практически в два раза больше аналогичной под фреймфорк 4.5.
Поэтому данная замена имеет место быть, так как позволит избавиться от одной лишней библиотеки и возложит всю работу на сам фреймворк. Мы же получим оптимизированную версию, которая будет предоставлять нам новые возможности, благодаря новому фреймворку.
VirtualizingPanel
Одна из самых популярных проблем при отображении большого количества данных в гридах и т.д. – это прокрутка данных гридов, чтобы они не подвисали. Ниже представлен пример такого грида с большим количеством данных, картинку которого я взял со stackoverflow, чтобы продемонстрировать, чем это чревато.
В старой версии фреймфорка мы могли только установить свойство IsVirtualizing и VirtualizationMode.  В простых случаях она спасает. Но в тех контролах, где есть группировка контролов, разного вида фильтры, это не особо помогает. В 4.5 фреймворк для панелей, наследуемых от VirtualizingPanel, добавилось несколько свойств, которые могут ускорить намного прорисовку контролов во время прокрутки.
  1. You can specify whether a VirtualizingPanel, such as the VirtualizingStackPanel, displays partial items by using the ScrollUnit attached property. If ScrollUnit is set to Item, the VirtualizingPanel will only display items that are completely visible. If ScrollUnit is set to Pixel, the VirtualizingPanel can display partially visible items.    
  2. You can specify the size of the cache before and after the viewport when the VirtualizingPanel is virtualizing by using the CacheLength attached property. The cache is the amount of space above or below the viewport in which items are not virtualized. Using a cache to avoid generating UI elements as they’re scrolled into view can improve performance. The cache is populated at a lower priority so that the application does not become unresponsive during the operation. The VirtualizingPanel.CacheLengthUnit property determines the unit of measurement that is used by VirtualizingPanel.CacheLength.
Асинхронная и синхронная валидация данных.
Добавлен новый интерфейс INotifyDataErrorInfo, который позволяет валидировать данные на форме асинхронно.
Для пользователей, по сути, больше нет других полезных вещей, которые они могут увидеть. Но теперь пора перечислить те вещи, которые упростят жизнь разработчикам.
Отложенный байндинг
Одной из новых фишек, которые затронули WPF и которые могут пригодиться для разработчиков, – это использование отложенного байндинга. Например, вы вводите какой-то текст в TextBox и хотите его изменять в другом месте. Вы по старинке устанавливаете UpdateSourceTrigger в PropertyChanged и получаете обновление после каждого обновления свойства (добавление символа, вставка текста, удаление символа и т.д.). А если у вас при этом используется, например, какой-то фильтр, то у вас на каждую букву будет попытка найти отфильтрованные данные для списка. Скажем, я ввел 3 буквы и у меня три раза перестроились какие-то мои отфильтрованные данные в списке. Благодаря свойству Delay мы можем указать отложенное время, через которое необходимо обновить, например, некий фильтр или проделать нужные нам действия. Например, если мы поставим для Delay значение 1500 мс, то наше свойство будет изменено спустя 1,5 сек. За это время мы можем ввести порядка 5-7 символов. Аналогичная возможность уже была сделана в библиотеке реактивных расширений Rx (англ. The Reactive Extensions (Rx)) под WPF. Там это уже было сделано. Но, к сожалению, библиотека реактивных расширений не настолько популярна в мире разработки ПО, а еще мене популярна она для WPF.
Async Methods for Dispatcher
В классе Dispatcher, который вы, наверняка, используете в своем проекте, наконец-то появились нормальные асинхронные методы.
  • InvokeAsync(Action)
  • InvokeAsync(Action, DispatcherPriority)
  • InvokeAsync(Action, DispatcherPriority, CancellationToken)
  • InvokeAsync<TResult>(Func<TResult>)
  • InvokeAsync<TResult>(Func<TResult>, DispatcherPriority)
  • InvokeAsync<TResult>(Func<TResult>, DispatcherPriority, CancellationToken)
Вы, вероятнее всего, скажете, что асинхронность уже была доступна в Dispatcher с помощью методов BeginInvoke/EndInvoke, и будете правы. Теперь Dispatcher можно вызывать в асинхронном режиме, не дожидаясь ответа. Пожалуй, остановлюсь на этом моменте белее детально, поскольку надеюсь, что это нюанс будет интересен WPF-разработчикам. Класс Dispatcher наконец то начал нормально работать с тасками (класс Task с библиотеки Task Parallel Library (TPL)). У нас появился новый класс DispatcherOperation, который возвращает нам метод InvokeAsync класса Dispatcher.
public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
{
    if (callback == null)
    {
        throw new ArgumentNullException("callback");
    }
    ValidatePriority(priority, "priority");

    DispatcherOperation operation = new DispatcherOperation(this, priority, callback);
    InvokeAsyncImpl(operation, cancellationToken);

    return operation;
}
Пример использования InvokeAsync приведен ниже.
private async Task TestNewDispatcherAsync()
{
    var doSomething = await Dispatcher.CurrentDispatcher.InvokeAsync<Task<string>>(DoSomething);

    var result = await doSomething;

    MessageBox.Show(result);
}
private async Task<string> DoSomething()
{
    await Task.Delay(1000);
    return "hi";
}
Это работает по одной простой причине, что в .NET 4.5 появились методы async/await, и мы можем оперировать с помощью класса Dispatcher работой с тасками. 
Я считаю, что это неплохой способ использовать возможности класса Dispatcher по максимуму. И новые расширения должны вам поспособствовать разрабатывать ваши приложения легко читаемыми и легкими в сопровождении. Это немаловажно в программировании.
Налажена работа с WeakEventManager
Небольшие изменения коснулись также класса WeakEventManager, который используется для того, чтобы избежать жесткой привязки к событиям, и не следить за тем, что мы вдруг забудем отписаться от события. Использовать вместо самописных реализаций, например.
Garbage Collector Improvements
Улучшилась сборка мусора с помощью GC. Появился так называемый Background Server GC.
Кратко о преимуществе с habrahabr: Обратная сторона луны
Server GC разделяет управляемую кучу на сегменты, количество которых равно количеству логических процессоров, используя для обработки каждого из них по одному потоку.
Т.к. Server GC делит управляемую кучу на несколько сегментов (каждый их которых обслуживает отдельный логический процессор), то в параллельной сборке мусора уже нет необходимости (если бы еще один поток запускался на др. логическом процессоре). Этот режим принудительно переопределяет параметр gcConcurrent. Если включен режим gcConcurrent, режим Batch будет препятствовать дальнейшей параллельной сборке мусора (!!!). Batch эквивалентен непараллельной сборке мусора на рабочей станции. При использовании такого режима характерна обработка больших (!!!) объемов данных.
Asynchronous Programming with async/await
Новый подход к разработке параллельных программ, используя ключевые слова async/await.
  • Task строит цепочку продолжений, которая увеличивается в соответствии с количеством задач, связанных последовательно, и состояние системы управляется через замыкания, найденные компилятором;
  • async/await строит машину состояний, которая не использует дополнительных ресурсов при добавлении новых шагов. Однако компилятор может определить больше переменных для сохранение в стеки машины состояний, в зависимости от вашего кода (и компилятора). В статье на MSDN отлично расписаны детали происходящего.
Если вкратце, то данный подход позволяет писать код, который будет работать в асинхронном режиме и при этом не нагружать основной GUI поток.
Использование CallerMemberNameAttribute
Данный код позволит избегать вызова PropertyChanged в передаче аргумента по умолчанию. Это лучше рассмотреть на примере.
public class EmployeeVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
        }
    }
}
Выше приведен код, присущий в 4-м фреймворке. А теперь приведу пример, как изменится наш код с CallerMemberName.
public class EmployeeVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }
}
Или, например, можно взять методы, которые реализованы в библиотеке Prism 5.0.
private string _author;
public string Author
{
    get { return _author; }
    set
    {
        _author = value;
        OnPropertyChanged(() => Author);
    }
}
Или же вот такой тип записи:
private string _author;
public string Author
{
    get { return _author; }
    set
    {
        SetProperty(ref _author, value);
    }
}
Set Default Culture
Установку культуры для AppDomain можно сделать в одну строку, используя свойство DefaultThreadCurrentCulture. Позволяет избавиться от грязных хаков в программе, которые позволяли считывать культуру как Private Field и заменять это значение. Теперь с этим не нужно заморачиваться, просто используем свойство, которое появилось в новом фреймворке и которое мы сейчас рассматриваем.
Zip Compression
Поскольку поддержка работы с zip-архивами добавлена в сам фреймворк, мы можем избавиться от сторонних библиоте, как, например, Ionic.Zip, SharpCompress, SharpZipLib и других, если такие имеются в проекте.
TPL (Task Parallel Library)
В библиотеку TPL добавлено множество методов, которых очень сильно не хватало в 4-й версии. Например, методы FromResult, Delay, Yield, GetAwaiter и ConfigureTaskAwaiter. 
Итоги
Перечисленные выше преимущества позволят вам взглянуть на разработку ваших программ с использованием .NET Framework 4.5 под другим углом. Если у вас используются, например, WCF сервисы, то вы можете строить операции, которые будут возвращать таски. Также для работы с БД есть возможность использовать EF 6.0 с его новыми возможностями, как асинхронность, поддержка enums и многое другое. Есть, конечно, и негативные стороны в этом всем. Например, если у вас используются Moq для юнит или интеграционного тестирования, то вам нужно посмотреть в сторону обновления до последней версии (на момент написания статьи это была версия 4.2) данной библиотеки, так как она очень неплохо, наконец-то, стала работать с асинхронностью. Также MS Test позволяет тестировать код, который использует таски с помощью ключевых слов async/await, не изобретая для этого "велосипедов", которые параллельное выполнение вашего кода делают последовательным. Наверное, больше проблем можно схлопотать, если использовать UIAutomation для покрытия вашего интерфейса Coded UI Tests. Если не ходить вокруг да около, то компания Microsoft сломала обратную совместимость в данном плане для Grid, List для поиска дочерних элементов, а также сломана работа с ScrollView для данных контролов. Вам нужно будет придумывать разные костыли, чтобы это обойти. Как говорится, не все так гладко, как хотелось бы, но в целом, данный переход оправдан, если только ваши клиенты не используют Windows XP. В .NET 4.5 нет поддержки Windows XP. Это большая проблема для тех, у кого множество клиентов использует до сих пор Windows XP. Таким разработчикам можно разве что посочувствовать в постоянной адаптации новых возможностей, так как даже в 4-м фреймворке, если у вас все работает в WPF, проверьте это же на XP, и вы будете приятно удивлены. А учитывая тот факт, что даже после того как Microsoft прекратила поддержку Windows XP, для того чтобы пользователи перешли на ту же Windows 7, для некоторых пользователей это может занять годы. Надеюсь, что обзор был не очень скучным, и если вы даже не собираетесь переводить ваш проект на новый фреймворк, я думаю вам будет интересно знать, что особенного там появилось. 

No comments:

Post a Comment