среда, 15 октября 2014 г.

Стек и куча в .Net

Стек - это карманы. Туда сложена мелочь, которую можно быстро достать и расплатиться. Куча - это рюкзак, в который можно сложить большие вещи. Мелочь там тоже может быть, но упакованная в кошелек.

понедельник, 29 октября 2012 г.

Памятка: Разработка фоновых задач. Реализация классов из пространства имен Windows.ApplicationModel.Background.

Обобщенный процесс разработки  и использования в своем коде фоновой задачи

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

  • Добавляем в Solution еще один проект ClassLibrary;
  • В свойствах проекта устанавливаем “Output type” в Windows Runtime Component, чтобы у нас при сборке получался .winmd файл, а не dll’ка;
  • Создаем класс который реализует IBackgroundTask.

Регистрируем в системе фоновую задачу:

  • В манифесте основного (именно основного, не нужно в манифест библиотеки прописывать использование фоновых задач) приложения отмечаем, что будем использовать фоновые задачи;
  • В коде основного приложения используя BackgroundTaskBuilder создаем экземпляр фоновой задачи;
  • Устанавливаем триггеры при срабатывании которых задача будет запускаться;
  • Устанавливаем условия запуска фоновой задачи (если нужно, они могут быть пустыми);
  • Регистрируем в системе фоновую задачу;
  • Подписываемся на события.

Практически все, что нужно для реализации фоновых задач собрано в пространстве имен Windows.ApplicationModel.Background.

Пространство имен содержит:

Реализация интерфейса IBackgroundTask.

Интерфейс содержит всего один метод Run(IBackgroundTaskInstance taskInstance). Именно этот метод выполняется при запуске фоновой задачи.

Следует отметить, что наследование фоновых задач не допускается и класс реализующий интерфейс должен быть помечен модификатором sealed.

Параметр taskInstance содержит информацию о самой задаче (триггеры и условия запуска и тд.), как ее использовать мы посмотрим далее.

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance)
{
//Здесь код выполнения
}
}

Осталось 13 классов, 3 делегата, 4 перечисления и 3 интерфейса

Run может быть помечен, как async в этом случае следует использовать класс BackgroundTaskDeferral который оповещает систему о том, что работа еще не закончилась.

Выглядит это примерно так:

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
try
{
await FuncNameAsync();
}
finally { deferral.Complete(); }
}
}

Если вы используете модель асинхронных вызовов предлагаемую WinRT, то код будет выглядеть примерно так:

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
IAsyncAction op1 = Something.DoAsync();
op1.Completed = new AsyncActionCompletedHandler(
(IAsyncAction act, AsyncStatus stat) =>
{
deferral.Complete();
});
}
}

BackgroundTaskDeferral содержит всего один метод Complete();


В принципе, вам не обязательно вызывать этот метод в реализации Run. Если у вас сложная система колбеков то оповестить систему можно где угодно.


Осталось 12 классов, 3 делегата, 4 перечисления и 3 интерфейса


Регистрация фоновой задачи в системе:


Лучше всего пожалуй будет разбирать на примере кода, вот наиболее раздутый пример:

private void RegisterBackgroundTasks() 
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
//Название фоновой задачи, может не совпадать с именем класса
builder.Name = "BackgroundTestClass";
// Имя класса
builder.TaskEntryPoint = "BackgroundTaskLibrary.TestClass";
IBackgroundTrigger trigger = new TimeTrigger(15, true);
builder.SetTrigger(trigger);
IBackgroundCondition condition = new SystemCondition(SystemConditionType.InternetAvailable);
builder.AddCondition(condition);
IBackgroundTaskRegistration task = builder.Register();
task.Progress += new BackgroundTaskProgressEventHandler(task_Progress);
task.Completed += new BackgroundTaskCompletedEventHandler(task_Completed);
}

Класс BackgroundTaskBuilder позволяет регистрировать фоновую задачу в системе.

У класса есть конструктор, 2 свойства (Name и TaskEntryPoint) и 3 метода:


  1. SetTrigger(IBackgroundTrigger trigger) – устанавливает триггер по срабатыванию которого начинает работу (проверив условия заданные при помощи AddCondition) фоновая задача;
  2. AddCondition(IBackgroundCondition condition) – задаёт условие при которых должна выполнятся фоновая задача;
  3. Register() – Регистрирует фоновую задачу в системе. Возвращает BackgroundTaskRegistration (который реализует IBackgroundTaskRegistration ).

Используя BackgroundTaskRegistration можно отменять регистрацию фоновой задачи при помощи вызова метода Unregister или перебирать все зарегистрированные приложением фоновые задачи используя свойство AllTasks.

Перечисление всех зарегистрированных фоновых задач обычно используется, чтобы не регистрировать по два раза одну и ту же, в целях экономии ресурсов.

Пример кода:

private void RegisterBackgroundTasks() 
{
string taskName = "BackgroundTestClass";
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == taskName)
{
return;
}
}
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = taskName;
//и тд…
}

Осталось 11 классов, 3 делегата, 4 перечисления и 2 интерфейса

 


Триггеры исполнения фоновых задач


Всего Windows.ApplicationModel.Background содержит 6 классов реализующих IBackgroundTrigger :






















MaintenanceTrigger Представляет собой триггер оповещающий о возможности произвести обслуживание системы.
NetworkOperatorHotspotAuthenticationTrigger Представляет триггер срабатывающий при аутентификации в беспроводной сети.
NetworkOperatorNotificationTrigger Представляет триггер срабатывающий на получении уведомлений от оператора связи.
PushNotificationTrigger Представляем собой триггер срабатывающий при получении приложением сырого уведомления (raw notification).
SystemTrigger Триггер реагирующий на системные события.
TimeTrigger Триггер срабатывающий через определенные интервалы времени.

 TimeTrigger

Класс срабатывающий по истечении временного интервала. TimeTrigger можно использовать только в приложениях размещенных на экране блокировки.


Конструктор

public TimeTrigger(
uint freshnessTime,
bool oneShot
)

принимает два параметра: первый указывает интервал исполнения в минутах, второй – флаг указывающий, что задача должна выполнится только 1 раз. Если параметр oneShot установить в false, то задача будет выполняться раз в freshnessTime минут.


Пример регистрации:

private bool RegisterTimeTriggerBackgroundTask()       
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "Background task with TimeTrigger";
builder.TaskEntryPoint = "LibName.TimeTriggerBackgroundTask";
// Запускаем раз в 15 минут
IBackgroundTrigger trigger = new TimeTrigger(15, false);
builder.SetTrigger(trigger);
IBackgroundTaskRegistration task = builder.Register();
return true;
}

MaintenanceTrigger


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


Параметры конструктора идентичны и не требуют пояснений.

public MaintenanceTrigger(
uint freshnessTime,
bool oneShot
)

Пример регистрации:

private bool RegisterMaintenanceBackgroundTask()
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "Maintenance background task";
builder.TaskEntryPoint = "LibName.MaintenaceBackgroundTask";
// Запускается каждые 8 часов, если устройство подключено к сети
IBackgroundTrigger trigger = new MaintenanceTrigger(480, false);
builder.SetTrigger(trigger);
IBackgroundTaskRegistration task = builder.Register();
return true;
}

Примечание: Триггеры фоновых задач, привязанные к временным интервалам (MaintenanceTrigger, TimeTime) не могут отрабатывать чаще, чем раз в 15 минут. При попытке зарегистрировать задачу с использованием интервала меньше 15 минут вылетит Exeption.


SystemTrigger


Представляет собой триггер реагирующий на системные события.


Конструктор

public SystemTrigger(
SystemTriggerType triggerType,
bool oneShot
)

Принимает два параметра: первый содержит системное событие из перечисления SystemTriggerType, второй, также как  и у TimeTrigger’a, указывает один ли раз срабатывать.


Привожу полный перечень событий:




























































СобытиеЗначениеОписание
Invalid0

Недопустимое значение

SmsReceived1

Фоновая задача срабатывает при получении SMS

UserPresent2

Срабатывает, когда пользователь входит в систему (снимает экран блокировки).


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

UserAway3

Срабатывает, когда пользователь покидает систему (появляется экран блокировки).


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

NetworkStateChange4

Срабатывает, когда изменяются параметры сети, например отключение/подключение или изменяется стоимость трафика при переходе в роуминг.

ControlChannelReset 5

Срабатывает при перезагрузке канала связи.


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

InternetAvailable6

Фоновая задача срабатывает, когда интернет становится доступен.

SessionConnected 7

Срабатывает, когда пользователь логинится.


Примечание: Приложение должно быть помещено на экран блокировки, чтобы можно было регистрировать фоновую задачу с этим триггером

ServicingComplete 8

Фоновая задача срабатывает после окончания обновления приложения.

LockScreenApplicationAdded 9

Срабатывает, при добавлении приложения на экран блокировки.

LockScreenApplicationRemoved 10

Срабатывает, когда приложение удаляется с  экрана блокировки.

TimeZoneChange 11

Срабатывает при изменении временной зоны на устройстве (например, при переходе с зимнего на летнее время).

OnlineIdConnectedStateChange 12

Срабатывает когда изменяется статус подключения аккаунта Microsoft (бывший LiveId).


Пример регистрации:

private bool RegisterSystemTriggerBackgroundTask()
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = "SystemTrigger background task";
builder.TaskEntryPoint = "LibName.SystemBackgroundTask";
IBackgroundTrigger trigger = new SystemTrigger(InternetAvailable, false);
builder.SetTrigger(trigger);
IBackgroundTaskRegistration task = builder.Register();
return true;
}

PushNotificationTrigger


Представляет собой триггер, срабатывающий при получении Row Notification.

Имеет 2 конструктора:

public PushNotificationTrigger()
public PushNotificationTrigger(string applicationId)

Второй конструктор в качестве параметра получает идентификатор приложение (Package Relative Application ID (PRAID)) и выполняет фоновые действия для него. Следует отметить, что приложение из которого регистрируется фоновая задача и приложение идентификатор которого передается в конструктор должны находится в одном пакадже.

Последние 2 триггера

NetworkOperatorHotspotAuthenticationTrigger

NetworkOperatorNotificationTrigger

я пожалуй описывать не стану, т.к. информации по ним очень мало, достаточно сказать, что они нужны разработчикам приложений поставщиков мобильной связи.

Дополнительную информацию можно посмотреть здесь.

Осталось 5 классов, 3 делегата, 3 перечисления и 1 интерфейс


Условия исполнения.


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


Условий на выполнение может быть указано  0 или более. Другими словами условия указывать необязательно.


Создается условие при помощи вызова конструктора SystemCondition (реализует интерфейс IBackgroundCondition) который в качестве параметра получает элемент перечисления SystemConditionType


После этого условие добавляется используя метод AddCondition у TaskBuilder’а. Чтобы задать несколько условий, необходимо создать несколько экземпляров SystemCondition и для каждого вызвать AddCondition.


Привожу полный список элементов перечисления:




































ЭлементЗначениеОписание
Invalid 0

Недопустимое значение условия

UserPresent 1

Фоновая задача может запускаться только если пользователь присутствует (рабочий стол не находится под экраном блокировки).

UserNotPresent2

Фоновая задача может запускаться только если пользователь отсутствует.

InternetAvailable 3

Фоновая задача может запускаться только если устройство подключено к интернету.

InternetNotAvailable 4

Фоновая задача может запускаться только если устройство не подключено к интернету.

SessionConnected 5

Фоновая задача может запускаться если пользователь открыл рабочую сессию (ввел логин и пароль).

SessionDisconnected 6

Фоновая задача может запускаться, только если нет залогиневшегося пользователя.


Пример использования:

var sampleTaskBuilder = new BackgroundTaskBuilder();
//[skip]
//Создаем условия исполненияSystemCondition userCondition = new SystemCondition(UserPresent);
SystemCondition internetCondition = new SystemCondition(InternetAvailable);
//Устанавливаем условия
sampleTaskBuilder.AddCondition(userCondition);
sampleTaskBuilder.AddCondition(internetCondition);

Осталось 4 класса, 3 делегата,2 перечисления и 1 интерфейс


Обработка прогресса выполнения фоновой задачи


Для того, чтобы отслеживать прогресс исполнения фоновой задачи необходимо подписаться на событие Progress класса BackgroundTaskRegistration (реализует интерфейс IBackgroundTaskRegistration) используя делегат BackgroundTaskProgressEventHandler


В качестве параметров обработчик события получает 2 параметра: экземпляр IBackgrowndTaskRegistration и BackgroundTaskProgressEventArgs.


У BackgroundTaskProgressEventArgs есть свойство Progress, которое и содержит процент выполнения задачи.


Пример кода:

private void task_Progress(IBackgroundTaskRegistration task, BackgroundTaskProgressEventArgs args)          
{
var progress = "Progress: " + args.Progress + "%";
BackgroundTaskSample.ServicingCompleteTaskProgress = progress;
UpdateUI();
}

Осталось 3 класса, 2 делегата,2 перечисления и 0 интерфейсов


Обработка окончания выполнения фоновой задачи


Для того, чтобы обработать результаты выполнения фоновой задачи необходимо подписаться на событие Completed класса BackgroundTaskRegistration  используя делегат BackgroundTaskCompletedEventHandler. В качестве параметров обработчик получает все тот же экземпляр IBackgrowndTaskRegistration и BackgroundTaskCompletedEventHandler.


Типовым решением является сохранение результатов исполнения в LocalStttings используя в качестве ключа идентификатор фоновой задачи (BackgroundTaskRegistrationю.TaskId).


Чтобы проверить, что фоновая задача не вызвала исключение, следует вызывать метод BackgroundTaskCompletedEventHandler.CheckResult(), который пробрасывает исключение с которым завершилось выполнение фоновой задачи в основное приложение.


Пример кода:

private void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
var settings = ApplicationData.Current.LocalSettings;
var key = task.TaskId.ToString();
try
{
args.CheckResult();
//Получаем результат исполнения
var result = settings.Values[key].ToString();
//Используем результат при необходимости
}
catch (Exception ex)
{
//сообщаем об ошибке
}
}

Осталось 2 класса, 1 делегат,2 перечисления и 0 интерфейсов


Отмена выполнения фоновой задачи


Отмена выполнения фоновой задачи может происходить по нескольким причинам. Обработку отмены нужно производить в классе реализующем IBackgroundTask, для этого нужно подписаться на событие Canceled экземпляра IBackgroundTaskInstans передаваемого в метод Run.


Обработчик события имеет тип BackgroundTaskCanceledEventHandler и получает два параметра IBackgroundTaskInstance и элемент перечисления   BackgroundTaskCancellationReason.


Собственно в перечислении и содержится причина по которой запрошена отмена выполнения фоновой задачи.


Привожу полный список :
























ЧленЗначениеПричина
Abort0

Фоновая задача была отменена приложением. Это может произойти по четырем причинам:



  • Фоновая задача определяется системой как бездействующая.
  • Фоновая задача активировалась при помощи MaintenanceTrigger но во время выполнения система перешла на работу от батареек.
  • Приложение отменило регистрацию фоновой задачи, и потому выполнение отменяется.
  • Приложение которое зарегистрировало фоновую задачу деинсталировано во время выполнения.
Terminating 1

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

LoggingOff 2

Выполнение фоновой задачи произошло, потому-что пользователь вышел из системы.

ServicingUpdate 3

Отмена фоновой задачи произошла из-за того, что приложение обновляется.


Типовым решение для обработки отмены является использования флага в классе реализующем IBackgroundTask с периодическим опрашиванием в методе Run значения этого флага.


Пример кода:

public sealed class BackgroundTaskRealisation : IBackgroundTask
{
volatile bool isCancelRequested = false;
void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance)
{
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(task_Canceled);//Здесь код выполнения
Operation1();
if (!isCancelRequested)
Operation2();
//и тд…
}

private void task_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
isCancelRequested = true;
}
}

Отмена из основной программы делается следующим образом:

string name = "ExampleTaskName";
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == name)
{
cur.Value.Unregister(true);
}
}

Осталось 1 класса, 0 делегат,1 перечисление и 0 интерфейсов


Итак для изучения у нас остался последний класс BackgroundExecutionManager, который предоставляет методы для доступа к экрану блокировки. Класс предоставляет 6 методов, которые можно разбить на 3 группы:



  • Запрос доступа к экрану блокировки

    1. RequestAccessAsync()
    2. RequestAccessAsync(String)
    В этом случае пользователю показывается диалоговое окно с вопросом о разрешении добавить приложение на экран блокировки.
  • Проверка статуса доступа к экрану блокировки

    1. GetAccessStatus()
    2. GetAccessStatus(String)
    Окно не показывается, но проверка производится. Функция нужна, т.к. после разрешения или отказа от размещения на экране блокировки, состояние доступа может быть изменено в настройках системы.
  • Отмена доступа к экрану блокировки

    1. RemoveAccess()
    2. RemoveAccess(String)

Если функция вызывается без параметра, то делается это для приложения из которого она вызывается, но можно и запросить или удалить доступ для другого приложения, если указать его идентификатор в качестве параметра.


Функции запроса и проверки статуса возвращают перечисления (члены перечисления? ) BackgroundAccessStatus.


Привожу все возможные состояния:
























НазваниеЗначениеОписание
Unspecified0

Пользователь не стал нажимать кнопки "разрешить" или  "не разрешать"  в диалоговом окне, просто закрыв его.


Фактически доступа в этом случае нет. Нужно снова запрашивать доступ при помощи  RequestAccessAsync().

AllowedWithAlwaysOnRealTimeConnectivity 1

Пользователь выбрал "разрешить" в диалоговом окне. Приложение добавлено на экран блокировки и может , ему доступно использование RTC брокера  в режиме пониженного энергопотребления  (connected standby).


Чтобы получить такой статус нужно чтобы в манифесте приложения указывалось, что необходим доступ к RTC. Из восьми приложений располагающихся на экране блокировки доступ к RTC в режиме пониженного энергопотребления могут получить только три, поэтому, даже если вы правильно все указали в манифесте, система может и не выдать вам такое разрешение.


При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно.

AllowedMayUseActiveRealTimeConnectivity 2

Пользователь выбрал "разрешить" в диалоговом окне. Приложение может запускать фоновые задачи, в  режиме пониженного энергопотребления приложение может не работать.


При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно.

Denied 3

Пользователь выбрал "не разрешать" в диалоговом окне. Приложение не добавлено на экран блокировки.


При попытке вызвать RequestAccessAsync пользователю больше не будет показываться диалоговое окно.


Пример кода:

BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
switch (status)
{
case BackgroundAccessStatus.AllowedWithAlwaysOnRealTimeConnectivity:
//Есть доступ к RTC, даже в режиме пониженного энергопотребления
lockScreenAdded = true;
break;
case BackgroundAccessStatus.AllowedMayUseActiveRealTimeConnectivity:
//Есть доступ к RTC, но не в режиме пониженного энергопотребления
break;
case BackgroundAccessStatus.Denied:
//Ой беда, беда… Огорчение…
break;
}
Осталось 0 класса, 0 делегатов, 0 перечислений и 0 интерфейсов.

      понедельник, 13 августа 2012 г.

      Промокоды для Beta экзаменов

      В блоге BornToLearn cтали доступны промокоды для еще нескольких экзаменов. Привожу полный список.

      Номер экзамена Название Код Окончание действия промокода
      70-481 Essentials of Developing Windows Metro style Apps using HTML5 and JavaScript FYT481 20.08.2012
      70-482 Advanced Metro style App Development using HTML5 and JavaScript GXZ482 17.08.2012
      70-483 Programming in C# JOK483 21.08.2012
      70-484 Essentials of Developing Windows Metro style Apps using C# FTT484 05.09.2012
      70-485 Advanced Metro style App Development using C# FTT485 05.09.2012
      70-486 Developing ASP.NET 4.5 MVC Web Applications WWW486 17.08.2012
      70-487 Developing Windows Azure and Web Services WWW487 04.09.2012

      Сам я пойду по ветке MCSD: Windows Metro Style Apps Using C# (70-483, 70-484, 70-485), поэтому ждать подборок по остальным экзаменам не стоит. Пока. Может позже.

      среда, 8 августа 2012 г.

      Доступны для сдачи beta экзамены на получение статуса Microsoft Certified Solution Developer (MCSD): Web Application

      В блоге Born To Learn появилась информация об обновленной ветке Microsoft Certified Solution Developer (MCSD): Web Application, которая приходит на смену MCPD: Web Developer 4.

      Для получения статуса MCSD нужно подтвердить знания MVC, Azure, HTML5 и CSS3 сдав 3 экзамена:

      В настоящий момент можно сдавать бесплатно (используя промокоды) экзамены 70-486 и 70-487. Результаты сдачи будут готовы к моменту окончания периода beta тестирования.

      При регистрации нужно использовать следующие коды:

      Экзамен 70-486: WWW486

      Экзамен 70-487: WWW487

      В публикации указано, что период тестирования будет до 12 августа, но я поставил 70-487 на двадцатое августа. В комментариях  к записи народ жалуется, что для 70-486 выдается ошибка о том, что срок действия промокода истек при попытке зарегистрироваться на любую дату.

      UPD: Сроки сдачи на самом деле такие: 70-486 до 17 августа, 70-487 до 23 августа

      понедельник, 10 октября 2011 г.

      Code Contracts и Visual Studio Express

      На сайте DevLabs (разработчиков Code Contracts) написано, что Visual Studio Express не поддерживается. Мне это показалось весьма странным, с учетом того, что System.Diagnostics.Contracts включен в .Net Framework 4. Конечно, Express версии не поддерживают PlugIn’ов, но ведь это совсем не повод, чтобы отказываться от использования CodeContract’ов, тем более, что все равно нужно выкачивать и устанавливать на машину разработчика то, что и называется CodeContracts. Установка содержит

      • ccrewrite утилита для осуществления проверки во время выполнения;
      • ccdoc утилита для генерации документации;
      • набор бибилиотек для .Net Framework’a 3.5 (по-моему, 3’й не поддерживается)

      Премиум также содержит cccheck статический анализатор времени компиляции;

      После установки в Visual Studio, в версиях выше Professional, в свойствах проекта появляется еще одна закладка: “Code Contracts”

       

      image

      Если отжать Perform Runtime Checking, то контракты начинаю срабатывать.

      Если после этого открыть проект в Express версии (или в #Develope ), то контракты продолжат работать, не смотря на то, что закладки “Code Contacts” в свойствах проекта не появляется.

      Итак, что же изменилось? А изменяется вот что: в файле проекта добавляются строки описывающие поведение контрактов.

      Чтобы увидеть изменения нужно открыть сам файл проекта (.csproj для С#) в текстовом виде:

      • В VS Professional и выше: выгружаем проект в Solution Explorer,  правой кнопкой щелкаем на проект и выбираем Edit ProjectName.csproj;
      • в Express версии, просто лезем в папку с проектом и открываем любым простейшим текстовым редактором.

      В каждую PrepertyGroup соответствующую вариантам сборки (X86/Debug/Release) добавились следующие строчки:

      <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
      <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface>
      <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure>
      <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires>
      <CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers>
      <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis>
      <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations>
      <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations>
      <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations>
      <CodeContractsEnumObligations>False</CodeContractsEnumObligations>
      <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions>
      <CodeContractsRunInBackground>True</CodeContractsRunInBackground>
      <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies>
      <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine>
      <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs>
      <CodeContractsCustomRewriterAssembly />
      <CodeContractsCustomRewriterClass />
      <CodeContractsLibPaths />
      <CodeContractsExtraRewriteOptions />
      <CodeContractsExtraAnalysisOptions />
      <CodeContractsBaseLineFile />
      <CodeContractsCacheAnalysisResults>False</CodeContractsCacheAnalysisResults>
      <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel>
      <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly>
      <CodeContractsAnalysisWarningLevel>0</CodeContractsAnalysisWarningLevel>

      Привожу, за что какие параметры отвечают:

      • CodeContractsEnableRuntimeChecking: если значение установлено в true, ccrewrite будет вживлять в проект проверку условий контракта. Фактически проверки выполнения контрактов начинаются с установки  этого значения и для того чтобы разобраться с использованием достаточно включить только его; 
      • CodeContractsRuntimeCheckingLevel: устанавливает уровни использования контрактов, то есть можно включить проверку только пред или постусловий. Принимаемые значения: Full, Pre and Post, Preconditions, ReleaseRequires или None;
      • CodeContractsRuntimeThrowOnFailure: если установлено в true, то контракт будет выбрасывать исключение. Если false, то проверка контракта будет вести себя как Debug.Assert;
      • CodeContractsRuntimeOnlyPublicSurface: если установлено true, то обрабатываются только публичные методы;
      • CodeContractsRuntimeCallSiteRequires: когда установлено true, реврайтер внедряет контракты в вызывающие ваш код приложения; ;
      • CodeContractsReferenceAssembly: принимает значения {Build, DoNotBuild, None}. Если выставить в Build, то создается дополнительная сборка с набором контрактов для этого проекта. Это нужно если на ваш проект ссылается еще один и ему нужно видеть, какие контракты используются или если вы хотите генерировать документацию по контрактам;
      • CodeContractsEmitXMLDocs: если установлено в true вместе с CodeContractsReferenceAssembly, генерируемая документацию к сборке будет содержать описание контрактов;
      • CodeContractsCustomRewriterAssembly: указывает пусть к сборке содержащей свою реализацию методов реврайтера;
      • CodeContractsCustomRewriterClass: содержит имя класса в котором реализованы методы, используется вместе с CodeContractsCustomRewriterAssembly;
      • CodeContractsLibPaths: здесь указываются через точку с запятой  пути к дополнительным сборкам содержащим контракты;
      • CodeContractsRunCodeAnalysis: значение включает статическую проверку кода собираемого проекта;
      • CodeContractsNonNullObligations: когда установлено true, статический анализатор будет пытаться проверять все ссылки на корректность;
      • CodeContractsBoundsObligations: когда установлено true, статический анализатор будет пытаться проверять корректность указания границ массивов;
      • CodeContractsArithmeticObligations: когда установлено true, статический анализатор будет пытаться проверять корректность математических операций, таких как деление на ноль;
      • CodeContractsRedundantAssumptions: когда установлено true, статический анализатор будет выдавать warning’и при “очевидных сравнениях” (не знаю как правильно перевести Redundant Assumptions) ;
      • CodeContractsRunInBackground: если установлено в true,  статический анализатор работает в фоновом процессе, не блокируя сборку проекта;
      • CodeContractsExtraAnalysisOptions: используется для передачи дополнительных параметров статического анализатора;
      • CodeContractsRuntimeSkipQuantifiers: если установлено в true, то некоторые контракты содержащие вызовы Contract.ForAll и Contract.Exists пропускаются. Это дает большую разницу в производительности;
      • CodeContractsEnumObligations: если установлено в true, статический анализатор проверяет границы перечислений; 
      • CodeContractsShowSquigglies: включает подчеркивание волнистой линией ошибок;
      • CodeContractsUseBaseLine: включает так называемую БазовуюЛинию. Она используется для того, чтобы статический анализатор мог отслеживать предупреждения только в изменяемом коде. При включении генерируется файл содержащий предупреждения и при последующих компиляциях они не отображаются;
      • CodeContractsBaseLineFile: имя файла в который записываются предупреждения;
      • CodeContractsExtraRewriteOptions: содержит дополнительные параметры для ccrewrite;
      • CodeContractsCacheAnalysisResults: включает кэширование статическим анализатором результатов;
      • CodeContractsAnalysisWarningLevel:  устанавливает уровень эвристики статического анализатора.

      Большинство этих параметров вносить не обязательно. Параметры, которые относятся к статическому анализатору можно точно откинуть, а остальные, за исключением CodeContractsEnableRuntimeChecking имеют вполне рабочие значения по умолчанию. Если вы хотите избавить свой проект от нагромождений if…throw,то достаточно использовать  CodeContractsEnableRuntimeChecking.

      Например.

      Создаем в Visual Studio Express новый проект консольного приложения с таким кодом:

      static void Main(string[] args)
      {
          PrintMessage("Привет");
          Console.ReadLine();
      }

      static void PrintMessage(string str)
      {
          Contract.Requires(str.Length > 10,"Message to short");
          Console.WriteLine("Сообщение: {0}",str);
      }

      Если приложение запустить, оно отлично отрабатывает, не смотря на то, что мы указали в контракте, что длина сообщения должна быть больше 10 символов.

      Сохраняем, закрываем VS Express, вносим изменения в файл проекта, снова открываем проект в VS Express.

      image

      Контракты начинают срабатывать.

      вторник, 2 августа 2011 г.

      Сохранение порядка сортировки выборки в Linq

      На MSDN’овском форуме задали вопрос о сохранении порядка сортировки выборки при использовании Linq. Задача достаточно типовая, поэтому решил написать небольшой мануал, как это сделать используя Dynamic Linq. Для упрощения пусть данные отображается у нас в DataGrid’e и сортируются нажатием мышкой на заголовок столбцов.

      Dynamic Linq

      Библиотека Dynamic Linq  входит в набор примеров кода для Visual Studio 2008 (C#, VB). После выкачивания можно найти исходный код, документацию и пример использования в папке \LinqSamples\DynamicQuery.

      Проект с примером использования отлично собирается как под .Net Framework 3.5, так и под 4.0.  Сам код библиотеки вынесен в namespace System.Linq.Dynamic и находится в файле Dynamic.cs. Для использования можно скопировать код к себе в проект или создать отдельный проект с библиотекой.

      Использование

      Итак у нас есть форма, на ней DataGrid с гордым именем dataGrid1.

      Простой класс:

      class Person
      {
          public string FirstName { get; set; }
          public string LastName { get; set; }
          public string NickName { get; set; }
          public DateTime BirthDate { get; set; }
      }

      При инициализации формы подсовываем Grid’у в качестве источника данных массив Person:

      var persons = new Person[] 
      {
          new Person{FirstName = "Сидр", LastName = "Сидоров", NickName = "sid", BirthDate = DateTime.Today.AddYears(-22)},
          new Person{FirstName = "Петр", LastName = "Петров", NickName = "pit", BirthDate = DateTime.Today.AddYears(-21)},
          new Person{FirstName = "Иван", LastName = "Иванов", NickName = "ian", BirthDate = DateTime.Today.AddYears(-20)},
      };
      this.dataGrid1.ItemsSource = persons;

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

      Итак, приступим.

      Первое, что нам нужно сделать, это перенести код библиотеки Dynamic Linq  к себе в проект. Это можно сделать двумя способами:

      1. Создать класс и скопировать код из полученного ранее проекта;
      2. Сразу добавить файл в себе в проект (в Solution Explorer’e щелкнуть право кнопкой на проект, и выбрать Add->Existing Item).

      У DataGrid’а добавляем в обработчик события Sorting код, который будет сохранять поле и порядок сортировки в конфигурационном файле

      private void dataGrid1_Sorting(object sender, DataGridSortingEventArgs e)
      {
          IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly();
          using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("LastSort.txt", FileMode.Create, store))
          {
              using (StreamWriter writer = new StreamWriter(stream))
              {
                  writer.WriteLine(String.Format("{0} {1}", e.Column.SortMemberPath,
                      (e.Column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending));
              }
          }
      }

      Небольшое пояснение по поводу IsolatedStorage
      IsolatedStorage – это механизм позволяющий сохранять данные в “виртуальные” папки в зависимости от выбранной области ограничения видимости. Использованная функция GetUserStoreForAssembly() возвращает нам “путь” который уникален для пользователя  и Assembly, т.е. другие приложения и пользователи не будут иметь доступ к файлу LastSort.txt в котором хранятся данные об используемом порядке сортировки.

      Небольшое пояснение по поводу (e.Column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending
      Дело в том, что Column.SortDirection является Nullable, то есть кроме ListSortDirection.Ascending и ListSortDirection.Descending принимает еще и значение NULL, которое и является значением по умолчанию. Помимо этого в  DataGridSortingEventArgs e содержится предыдущее состояние сортировки, поэтому приходится использовать такую конструкцию.

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

      private void Window_Closing(object sender, CancelEventArgs e)
      {
          foreach (var c in this.dataGrid1.Columns)
          {
              if (c.SortDirection.HasValue)
              {
                  IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly();
                  using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("LastSort.txt", FileMode.Create, store))
                  {
                      using (StreamWriter writer = new StreamWriter(stream))
                      {
                          writer.WriteLine(String.Format("{0} {1}", c.SortMemberPath, c.SortDirection));
                      }
                  }
                  break;
              }
          }
      }

      Пишем функцию которая выдергивает значение из файла в изолированном хранилище

      private string GetLastSort()
      {
          IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly();
          using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("LastSort.txt", FileMode.OpenOrCreate, store))
          {
              using (StreamReader reader = new StreamReader(stream))
              {
                  return reader.ReadLine() ?? "NickName Descending";
              }
          }
      }

      После этого меняем подключение данных для DataGrid’а на

      this.dataGrid1.ItemsSource = persons.AsQueryable<Person>().OrderBy(GetLastSort()).Select("new (FirstName, LastName, NickName, BirthDate)");

      среда, 20 апреля 2011 г.

      Экзамен 70-599: Pro: Designing and Developing Windows Phone 7 Applications, Часть 5: Designing the User Interface and User Experience

      >>В начало

      Designing the User Interface and User Experience (23%)

      • Design for separation of concerns.
        • This objective may include but is not limited to: presentation patterns that use view models, MVVM
      • Design Windows Phone 7 control usage.
        • This objective may include but is not limited to: design control usage as described in UI Design and Interaction Guide for Windows Phone 7; design proper use of PanoramaControl and PivotControl; choose when to use the Panorama Control and PivotControl; recommend when to use ApplicationBar
      • Recommend keyboard layout for a given situation.
        • This objective may include but is not limited to: InputScope property
      • Design for system themes, accent color, and screen orientation.
        • This objective may include but is not limited to: built-in styles that use system themes and accent colors, ApplicationBar icons (size, transparency), landscape, portrait

      Ссылки:

      Designing the User Interface and User Experience (23%)

      RU links: Video (EN)