Friday, February 28, 2014

Как я MCP получал

Здравствуйте, уважаемые читатели моего блога. Позвольте поделиться своими впечатлениями от прохождения сертификации компании Microsoft на статус MCP.
Сертифицированный профессионал Microsoft (англ. Microsoft Certified Professional, MCP) - квалификация сотрудника, подтверждённая корпорацией Майкрософт в виде сертификата и присваиваемая специалистам в области информационных технологий. Чтобы получить такую квалификацию, требуется успешно сдать один или несколько квалификационных экзаменов. Существуют различные типы сертификатов, различающихся как по уровню, так и по специализации. Для сдачи сертификации я выбрал язык C#, и в итоге мне пришлось сдавать экзамен Microsoft 070-483. Сейчас продолжаю готовиться дальше к сертификации MCSD:Windows Store Apps Using C#, с чем более детально можно ознакомиться здесь. Но чем больше углубляюсь в программирование на Windows Store Apps, тем больше мне хочется выбрать HTML5 + JavaScript, так как в плане Store Apps компания Microsoft позаботилась, чтобы как можно дальше оттолкнуть разработчиков.
Расскажу о самом процессе и подготовке к этой сертификации, которую, благо, я сегодня успешно сдал. В рекламе компаний, в которых можно сдать сертификацию в Киеве, первым в списке стоит Cyberbionic Systematics.
Зашел к ним на сайт, почитал отзывы, посмотрел, что предлагают другие фирмы и остановился на них. Меня привлекло оформление сайта, система работы онлайн-помощи и наличие пробных тестовых заданий, в которых можно себя проверить перед сертификацией. Как по мне, очень даже неплохо.
Вот какой вид имеет сайт Cyberbionic. Для того чтобы записаться на получение сертификации, необходимо зарегистрироваться на сайте Prometric. Первое, что смущает на этом сайте, - это трудности с поиском кнопки регистрации.
Создатели сайта, очевидно, не представляют, как расположение кнопок, которое они считают нормальным, немного не вкладывается в рамки мышления обычных людей. Каждый раз, когда заходишь на Prometric, пытаешься вспомнить, как заходил в предыдущий раз, потому что по привычке смотришь сверху, где же кнопочка Sign in/Register. Но таких кнопочек там нет. Если вы внимательно посмотрите на сайт, то сможете увидеть кнопочку "IT TEST TAKER Account Sign-In".
Спасибо огромное и огромный плюс Cyberbionic за подробную инструкцию, как это сделать. Наверное, дизайнеры сайта Prometric полагают, что контрол, который визуально напоминает радиокнопку, интуитивно подсказывает, что на него нужно нажать. Наверное, я не смыслю ничего в веб-дизайне, если не понимаю таких простых вещей. 
Второй интересный момент у меня случился при попытке зарегистрироваться на на ukr.net, когда мою регистрацию постоянно отбивало: то пароль "не нравился", то с какие то проблемы с моим e-mail. В итоге у меня заняло полчаса борьбы с Prometric, в которой я проиграл и решил отложить регистрацию к лучшему времени. На следующий день решил попробовать, как обстоят дела в Prometric с почтой на gmail. И тут случилось чудо, и регистрация на этом сайте прошла успешно. Поскольку я решил немного подготовиться к сертификации, то срок до экзамена выбрал через два месяца. Удобную дату и время можно выбрать на сайте Prometric
Если вы разберетесь и пройдете сертификацию на этом сайте, то зарегистрироваться на желаемый экзамен у вас точно выйдет, так как после этих операций процесс выбора экзамена кажется просто детским лепетом. Кстати, в инструкции, любезно предоставленной Cyberbionic для простой регистрации на нужный экзамен, вся эта информация есть. Просто следуйте инструкции, и у вас не будет никакой головной боли. 
Последним этапом остается оплата услуги. На момент моей оплаты услуга сдачи сертификации стоила ни много ни мало, 80$. Немаленькая сумма, чтобы не было обидно, если не сможешь пройти сертификацию.  С оплатой тоже есть некоторые нюансы. Чтобы у вас с этим не возникло проблем, желательно, чтобы у вас была карта VISA международного образца, которая должна быть валютной, лучше в долларах, потому что я не знаю, как проходят платежи с других валют. Завел эту карточку специально, чтобы делать покупки на amazon. Поэтому сразу после оплаты получил квитанцию об оплате на сайте  Prometric,  которую меня любезно попросили распечатать "на всякий случай". 
После этого стал готовиться к сертификации. Первым делом решил проверить, какие есть достойные книги по данному экзамену. Нашел две таких: Wouter de Kort, Exam Ref 70-483: Programming in C# и Tiberiu Covaci, MCSD Certification Toolkit (Exam 70-483): Programming in C# (Wrox Programmer to Programmer). Вот ка выглядят эти книги:
Буду краток по поводу этих книг. Они просто не стоят своих денег. А некоторые по количеству ошибок можно отнести вообще к шлаку. Полная рецензия на эти книги я написал в предыдущей своей статье Рецензия книг по подготовке к Microsoft Exam 70-483. После сдачи экзамена мое мнение об этих книгах не изменилось, даже наоборот, ухудшилось. Единственное, что можете почерпнуть из этих книг,- так это посмотреть по криптографии, шифрованию и т.д., так как в сертификации по этой теме есть вопросы и есть большая вероятность, что вы с ней не работали, так как это очень специфическая тема. 
Если вы хотите действительно легко сдать сертификацию, посмотрите лучше книгу Джона Скита "C# in Depth". Во-первых, она дешевле, а во-вторых, Скит гениальный автор, и читать его книги одно удовольствие, чего не скажешь о книгах, авторы которых приведены выше. Поэтому если у вас достаточно опыта разработки программного обеспечения, то Джон Скит неплохо поможет вам вспомнить некоторые нюансы языка C#.
Оставим литературу в стороне и поговорим о том, как проходит экзамен. Для начала вам нужно по приезде на место сертификации подтвердить личность. Для этого необходимо два документа: паспорт и загранпаспорт. Потом нужно подписать документы перед сдачей сертификации о том, что вас проинструктировали, после этого вас проводят в кабинет, где будет проходить сертификация. Экзамен проходит на компьютере, и после сдачи вы сможете сразу узнать результат, сдали вы сертификацию или нет. 
Экзамен длится 2 часа 45 минут, но с учетом всего времени, получается немного меньше. Всего на экзамен дается порядка 47 вопросов. Вопросы есть разной степени сложности. Есть некоторые вопросы с двояким толкованием. У меня таких вопросов было два. Один был связан с тасками, второй - со сборками (assemblies). Остальные вопросы были интуитивно понятны, и необходимо было только иметь необходимые знания, чтобы на них ответить. Если вы скачивали книги, которые приводятся выше, по подготовке к сертификации, то будьте готовы, что несуразных вопросов в этих книгах очень много. На экзамене всегда стоит четко поставленный вопрос, на который нужно выбрать правильный ответ. Если ответ должен включать 2 и больше варианта ответа, вас об этом предупредят. Вопросы большие и их нужно внимательно читать, правильное прочтение вопроса - это уже 50% ответа на поставленный вопрос. Есть вопросы по шифрованию, криптографии, хешировании и т.д. Если вы с этим не знакомы, то стоит об этом почитать перед экзаменом. Большинство вопросов построены по принципу дописывания программного кода. Вам дается программный код, и нужно перетащить недостающие блоки кода в нужные места. Таких вопросов очень много, поэтому если вы не писали код на языке C# до этого, то просто заучивание ответов по книгах вам не поможет, так как вы можете завалить экзамен. 
Примерный вариант вопроса на экзамене: дано условие, что на страницу выводится по 10 элементов из коллекции, а вам нужно с помощью написанных кусочков кода выстроить код, который возвратит вам третью страницу со всеми элементами. В общем, сложность вопросов самая разнообразная. От очень простых до очень сложных. Для экзамена вам выдадут маркер и лист, на котором можно делать заметки. Но мне он пригодился только для выписки двух вопросов, с формулировкой которых я был не согласен. После экзамена у вас будет возможность пройти анкету от Prometric и уточнить у них интересующие вас вопросы. Поскольку у меня было только два таких вопроса, на которые я знал ответы, я не задавал никаких дополнительных вопросов. Я прошел предложенную анкету из 24 вопросов, в которой предлагалось оценить уровень прохождения сертификации, было ли шумно во время сдачи, как мне понравились вопросы и так далее в таком духе. Также в конце экзамена, когда был продемонстрирован результат об успешной сдаче, мне отдали мои результаты в виде графика. Также результаты должны прийти на почту отдельно. По этому графику вы можете посмотреть свои сильные и слабые стороны, чтобы знать, в каком направлении развивать себе далее.
Спустя 3 дня компания Microsoft прислала мне мой MC ID, с которым я должен зарегистрироваться на https://mcp.microsoft.com/ с присланным мне Microsoft Certification ID (MC ID.), с которым у меня ничего с регистрацией не получилось, так как мне не выслали Access Code. Что это за  Access Code, не совсем понятно с описания, которое присылает Microsoft, но без него вы не сможете зарегистрироваться. Поскольку я так и не смог зарегистрироваться, то решил проверить, много ли специалистов с такой проблемой.
Я посмотрел как варианты на английском, так и на русском. У многих такая неувязка с регистрацией. Так что будьте готовы столкнуться с этой проблемой. Первым делом я решил написать в техподдержку с сайта 
Но как только я пытался выбрать регион проживания, меня сразу перекидывало на их партнерский сайт. Спустя полчаса это изрядно надоело, и я решил поискать в интернете прямую почту поддержки для MCP. На одном из сайтов нашел вот такой почтовый адрес: emeamcp@msdirectservices.com, и отправил на него письмо с результатами сдачи и описанием сложившейся ситуации. Письмо писал на английском с указанием экзамена, времени его прохождения и т.д. Некоторые отсылали сканированный документ, полученный в центре сертификации, но поскольку у меня сканера под рукой не оказалось, то необходимую информацию набрал вручную. Спустя 10-15 минут мне пришел ответ, в котором мне написали , что они рассматривают мой запрос и ответят, как только прояснят ситуацию. А спустя час пришел очередной спам от Майкрософт с предложением разных курсов сертификации и предложением оценить процесс сертификации, чего я не сделал, так как после безуспешной регистрации и кучей возни с их сайтами я был очень зол. Сегодня, когда попытался зайти на сайт для MCP, то увидел вот такое сообщение:

То есть даже сам сайт у них работает по времени. Это говорит о том, что компания Microsoft не очень стремится сотрудничать с разработчиками. На этом кропотливое получение сертификата не закончилось. Так что, как говорится, "to be continued…". В принципе, сертификация сдана, документы об успешной сдаче на руках и подтверждение пришло на почту. Так что могу считать себя MCP. А с регистрацией как MCP Member еще "прободаюсь". Интересно, какие преимущества дает компания Майкрософт сертифицированным специалистам, кроме бумажки об успешной сдаче. До следующих статей. Надеюсь, это краткое пособие поможет вам в подготовке к сертификации и ее успешной сдаче, если вы надумаете получить статус MCP.  

Wednesday, February 26, 2014

Введение в Roslyn

В этой статье поговорим об использовании компилятора как сервиса. Мы рассмотрим проект, над которым сейчас трудится компания Microsoft, под названием "Roslyn". Наконец-то у меня дошли руки, чтобы испробовать это чудо от первого лица и описать свои ощущения об этом проекте. В современном мире разработки программного обеспечения сложно раскрыть свои возможности и знания. Поэтому попробуем вместе окунуться в мир разработки ПО вместе с Roslyn.  С момента написания строчек выше прошло больше месяца, и только сейчас я поборол свой страх и решил снова вернуться к анализу Roslyn. Возможно, столь значительному перерыву "посодействовал" тот факт, что во время подготовки к написанию материала мой мозг работал не так активно, как обычно, из-за затянувшегося бронхита. 
Для понимания принципов работы Roslyn не нужно быть гуру в программировании на языке C#; для начала использования компилятора как сервиса в своих программах достаточно соответствующих базисных знаний. На момент описания статьи Roslyn доступен для предварительного просмотра, и его уже можно "пощупать". Не стоит спешить сразу включать его в свои проекты, так как окончательная версия, которую планируют, по слухам, выпустить в текущем 2014 году может значительно измениться. Но в случае с проектом Roslyn, это попытка упростить себе будущее, так как на данный момент это основной проект, над которым работает компания Microsoft. В рамках выхода в мир языка C# 6, будем надеяться, что компания Microsoft выпустит уже и Roslyn
Перейдем немного к теории и рассмотрим основные части, из которых состоит Roslyn.
Для начала опишу основную идею проекта Roslyn. Все мы знаем, что компилятор являет собой некий черный ящик, о реализации которого неизвестно и которому мы передаем наши файлы кода с одной стороны, а после некоторой "магии" со стороны компилятора мы получаем на выходе либо исполняемые файлы, либо библиотеки или другие файлы, в зависимости от компилятора и его возможностей. Основная миссия, которую на себя взял проект Roslyn, – это приоткрытые завесы тайны компиляторов и доступность возможностей этих компиляторов для взаимодействия с конечными пользователями и инструментами разработки через Ваш код. То есть сейчас Вы можете сами построить дерево, с которым будет работать Ваш компилятор, добавить разную валидацию для своей среды разработки и т.д. Единственное, что может Вас ограничивать, – это Ваша фантазия, и пока неполная поддержка некоторых ключевых возможностей языка C# (например, Roslyn пока не дружит с dynamic, async/await). Но, как обещает команда разработки, это все будет учтено в следующей версии. Проект Roslyn предоставляет Вам как потребителю анализ кода компилятора, таких языков как C# и Visual Basic предоставляя слой API, который отражает традиционный конвейер компилятора. Вот как это выглядит:
Это схема с документации компании Microsoft к Roslyn. На рисунке показаны четыре фазы конвейера (к сожалению, я не знаю, как корректно в данном контексте перевести слово pipeline, которое используется во всех примерах). Каждая фаза этого конвейера является отдельным компонентом. В первой фазе происходит разбор источника на лексемы со сбором в синтаксические инструкции с помощью языковой грамматики. Вторая фаза,  это фаза объявления, в которой импорт метаданных анализируются для формирования именованных символов. Третья фаза является фазой привязки, где идентификаторы в коде сопоставляются с символами. И последней выделяют фазу, где вся информация, построенная  компилятором, собирается в сборку (assembly).
Компилятор как сервис по использованию API разделяется на 4 части.
  • Compiler APIs
  • Scripting APIs
  • Workspace APIs
  • Services APIs
Compiler APIs отвечает за процесс сборки, делает семантический и синтаксический анализ кода.
Scripting APIs позволяет использовать возможности языка C# для написания скриптов.
Workspace APIs – набор APIs, позволяющий работать с такими сущностями, как проект и решение (project and solution).
Services APIs – это набор APIs, позволяющий расширять возможности Visual Studio (refactoring, code issue и другие).
В данной статье рассмотрим пример, который предоставляет компания Microsoft под названием How to Write a Quick Fix, который построен на использовании Service APIs. Мы создадим проект Code Issue, в котором с помощью extension для Visual Studio мы будем предлагать заметить обычное объявление переменной на константу , если это возможно.
int x = 0;
Console.WriteLine(x);
Заменим на
const int x = 0;
Console.WriteLine(x);
Создадим новое приложение Code Issue и назовем его FirstQuickFix.
Интересный момент с выбором 4-го фремворка как в примере, у меня в Visual Studio 2012. Для того чтобы ошибки не выпадали, пришлось выставить .NET Framework 4.5.  После выполненных действий нам необходимо перейти в файл source.extension.vsixmanifest.
Затем в этом файле находим строчку и изменяем параметр MinVersion на 4.0.
В документации Вы этого не найдете, так как все примеры были написаны под 2010 студию, возможно, там не нужно было делать таких "телодвижений". Пример, который создан по умолчанию, создает расширения для студии, где будет делать подсказку, если в методе есть буква "a". Если Вы запустите свой проект в дебаг-режиме, то у Вас запустится еще один экземпляр Visual Studio 2012, в котором Вы сможете создать простое консольное приложение и посмотреть результат.
Я назвал тестовый проект "TestRoslyn", а Вы по желанию можете дать ему другое название.
Подведите курсор к любому ключевому слову, где есть буква "a", и посмотрите на результат. Как по мне, то выглядит довольно эффектно. Если у Вас стоит решарпер, то придумать что-то новое будет сложно, но зато Вы сейчас можете написать основные экстеншены сами. Давайте разберем, как это работает.
С использованием Code Issue Provider связано несколько особенностей.
Каждая реализация ICodeIssueProvider должна предоставлять, по крайней мере, один атрибут Export, который описывает важные детали о поставщике.
Каждый Code Issue Provider должен реализовывать интерфейс ICodeIssueProvider. Этот интерфейс содержит три перегруженных метода, называемые GetIssues, каждый из которых работает с syntax nodes, tokens or trivia. Атрибут Export определяет, какие из этих методов будут вызываться.
Еще один интересный факт – это то, что те примеры, которые Вы можете загрузить с MSDN, не будут работать. На момент написания статьи команда, которая работает на Roslyn, уже изменила логику работы некоторых составляющих, но не затронула при этом примеры. Лучше смотрите те примеры, которые Вам установятся вместе с Roslyn под Вашу студию. Они актуальные и содержат последние изменения. 
Для начала удалим код, который использовался для поиска буквы "a", и немного его изменим. Начальный код должен иметь такой вид:
[ExportCodeIssueProvider("FirstQuickFix", LanguageNames.CSharp)]
class CodeIssueProvider : ICodeIssueProvider
{
    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
    {
           
    }

    public IEnumerable<Type> SyntaxNodeTypes
    {
        get
        {
            yield return typeof(LocalDeclarationStatementSyntax);
        }
    }

    #region Unimplemented ICodeIssueProvider members

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<int> SyntaxTokenKinds
    {
        get
        {
            return null;
        }
    }

    #endregion
}
Как видим, кроме того что мы удалили код для поиска буквы, мы также изменили тип, который будет возвращаться методом SyntaxNodeTypes, так как будем работать с объявлением локальных переменных и, если это возможно, предлагать конвертировать в константу.
Для начала добавим игнорирование тех переменных, которые уже установлены как константы.
var localDeclaration = (LocalDeclarationStatementSyntax)node;
// Only consider local variable declarations that aren't already const.
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
    return null;
}
Далее нам необходимо выполнить семантический анализ кода, чтобы определить , можем ли сделать локальную переменную константой. Для этого нам понадобится модель ISemanticModel.
Примечание: IDocument концептуально эквивалентно файлу в Visual Studio, и ISemanticModel является представлением всей семантической информации в одном исходном файле.
Для этого вызовем строчку кода
var semanticModel = document.GetSemanticModel(cancellationToken);
Далее необходимо убедиться, что каждая переменная в декларации имеет инициализатор. Это необходимо, чтобы соответствовать спецификации языка C#, в которой говорится, что все константные переменные должны быть инициализированы. Например, int х = 0, у = 1; можно сделать const, но int х, у = 1; – нельзя.
// Ensure that all variables in the local declaration have initializers that
// are assigned with constant values.
foreach (var variable in localDeclaration.Declaration.Variables)
{
    var initializer = variable.Initializer;
    if (initializer == null)
    {
        return null;
    }

    var constantValue = semanticModel.GetConstantValue(initializer.Value);
    if (!constantValue.HasValue)
    {
        return null;
    }
}
Строка кода
var constantValue = semanticModel.GetConstantValue(initializer.Value);
позволяет проверить, является ли переменная константой времени компиляции (compile-time constant).
Используем ISemanticModel для выполнения потока данных на локальном операторе объявления. Затем используем результаты этого анализа потока данных, чтобы гарантировать, что ни одна из локальных переменных не записывается с новым значением в другом месте. Вы сможете сделать это, вызвав метод ISemanticModel.GetDeclaredSymbol для получения ILocalSymbol для каждой переменной и убедившись, что она не содержится в WrittenOutside коллекции анализа потока данных.
// Perform data flow analysis on the local declaration.
var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
foreach (var variable in localDeclaration.Declaration.Variables)
{
    var variableSymbol = semanticModel.GetDeclaredSymbol(variable);
    if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
    {
        return null;
    }
}
Этот код не так прост для понимания, как предыдущие строки кода, но если его вдумчиво прочитать, то станет понятно, что имеется в виду. После проделанной работы мы можем вернуть новый CodeIssue, в котором выдадим пользователю предупреждение о том, что переменную можно сделать константой.
return new[]
{
    new CodeIssue(CodeIssueKind.Warning, localDeclaration.Span,
        "Can be made constant")
};
Полный метод выглядит следующим образом:
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
{
    var localDeclaration = (LocalDeclarationStatementSyntax)node;
    // Only consider local variable declarations that aren't already const.
    if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
    {
        return null;
    }

    var semanticModel = document.GetSemanticModel(cancellationToken);

    // Ensure that all variables in the local declaration have initializers that
    // are assigned with constant values.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var initializer = variable.Initializer;
        if (initializer == null)
        {
            return null;
        }

        var constantValue = semanticModel.GetConstantValue(initializer.Value);
        if (!constantValue.HasValue)
        {
            return null;
        }
    }

    // Perform data flow analysis on the local declaration.
    var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration);

    // Retrieve the local symbol for each variable in the local declaration
    // and ensure that it is not written outside of the data flow analysis region.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var variableSymbol = semanticModel.GetDeclaredSymbol(variable);
        if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
        {
            return null;
        }
    }

    return new[]
    {
        new CodeIssue(CodeIssueKind.Warning, localDeclaration.Span,
            "Can be made constant")
    };
}
После этого мы можем нажать F5, чтобы запустить наш проект в дебаг-режиме.

Благодаря команде, которая написала для нас подробную инструкцию, мы создали свой первый проект code issue, используя Roslyn APIs. Но поскольку из подсказок как таковых толку мало и приходится самому изменять код по подсказкам, давайте автоматизируем этот процесс. Любой Code Issue может включать несколько Code Actions, которые определяют изменения, которые будут применены к коду. Для этого нам необходимо в наш Code Issue проект добавить новый класс и наследоваться от интерфейса ICodeAction
Вот какая реализация получилась у меня по умолчанию, после того как я имплементировал интерфейс ICodeAction:
using System.Threading;
using Roslyn.Services;

namespace FirstQuickFix
{
    public class CodeAction : ICodeAction
    {
        public CodeActionEdit GetEdit(CancellationToken cancellationToken = new CancellationToken())
        {
            throw new System.NotImplementedException();
        }

        public string Description { get; private set; }
    }
}
Лишние пространства имен для наглядности я удалил с помощью ReSharper. Добавим описание, которое будет подсвечивать изменения.
public string Description
{
    get
    {
        return "Make Constant";
    }
}
Затем необходимо сказать созданному Code Issue использовать только что созданный CodeAction класс. Для этого переходим в класс CodeIssueProvider и изменяем наш созданный CodeIssue.
Затем с помощью горячих клавиш Ctrl+ сгенерируем необходимый конструктор. После этого переходим в класс CodeAction в метод GetEdit. Первым делом необходимо создать новый константный (const) маркер ключевого слова, который будет вставлен в передней части оператора объявления.
// Remove the leading trivia from the local declaration.
var firstToken = localDeclaration.GetFirstToken();
var leadingTrivia = firstToken.LeadingTrivia;
var trimmedLocal = localDeclaration.ReplaceToken(
    firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

// Create a const token with the original leading trivia.
var constToken = Syntax.Token(leadingTrivia, SyntaxKind.ConstKeyword);
Далее нам необходимо создать новый SyntaxTokenList в котором поместить созданный константный маркер в первую позицию.
// Insert the const token into the modifiers list, creating a new modifiers list.
var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
После этого создадим новый оператор объявления, который будет содержать новый список модификаторов.
// Produce the new local declaration.
var newLocal = trimmedLocal.WithModifiers(newModifiers);
Добавим форматирования для новой конструкции.
Извлечь корень CommonSyntaxNode с IDocument и использовать его, чтобы заменить старый оператор объявления на новый.
// Replace the old local declaration with the new local declaration.
var oldRoot = document.GetSyntaxRoot(cancellationToken);
var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);
И, наконец, создаем новый CodeActionEdit, в который передадим обновленный документ с преобразованием дерева.
// Create and return a new CodeActionEdit for the transformed tree.
return new CodeActionEdit(document.UpdateSyntaxRoot(newRoot));
Ниже приведен полный исходный код метода GetEdit.
public CodeActionEdit GetEdit(CancellationToken cancellationToken = new CancellationToken())
{
    // Remove the leading trivia from the local declaration.
    var firstToken = localDeclaration.GetFirstToken();
    var leadingTrivia = firstToken.LeadingTrivia;
    var trimmedLocal = localDeclaration.ReplaceToken(
        firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

    // Create a const token with the original leading trivia.
    var constToken = Syntax.Token(leadingTrivia, SyntaxKind.ConstKeyword);

    // Insert the const token into the modifiers list, creating a new modifiers list.
    var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);

    // Produce the new local declaration.
    var newLocal = trimmedLocal.WithModifiers(newModifiers);

    // Add an annotation to format the new local declaration.
    var formattedLocal = CodeAnnotations.Formatting.AddAnnotationTo(newLocal);

    // Replace the old local declaration with the new local declaration.
    var oldRoot = document.GetSyntaxRoot(cancellationToken);
    var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

    // Create and return a new CodeActionEdit for the transformed tree.
    return new CodeActionEdit(document.UpdateSyntaxRoot(newRoot));
}
Запустим проект и посмотрим, что из этого получилось.

Исправление ошибок
В приведенной реализации есть несколько ошибок.
1. Метод GetIssues  класса CodeIssueProvider не проверяет, если значение константы на самом деле нельзя привести к переменной типа. Так, текущая реализация будет удачливо преобразовать неправильное объявление например ‘int i = "abs" к локальной константе.
2. Ссылочные типы не обрабатываются должным образом. Пример объявление const string s = "abc" является валидным, а вызов ‘const object s = "abc нет.
3. Если переменная объявлена с ключевым словом "var", Code Action для такого кода делает не то, что мы ожидали, он создаст объявление "const var", не поддерживаемое в языке С#. Чтобы исправить эту ошибку, Code Action должен заменить ключевое слово "var" на соответствующий тип данных.
К счастью, все перечисленные выше ошибки могут быть решены с использованием тех же методов, что мы использовали в этой статье.
Чтобы исправить первый баг, сначала откроем CodeIssueProvider.cs и найдем цикл по каждому элементу, где проверяется каждая из локальных переменных для того, чтобы сделать их константами.
Перед циклом вызовем ISemanicModel.GetTypeInfo () для получения подробной информации о объявленном типе локальной переменной:
var variableTypeName = localDeclaration.Declaration.Type;
var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;
Затем добавим следующий код перед закрывающей фигурной скобкой цикла. Нам необходимо вызвать ISemanticModel.ClassifyConversion () и определить, является ли инициализатор приводимым к локальной переменной. Если преобразование невозможно или оно определяется пользователем, переменная не может быть локальной константой.
// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
var conversion = semanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
    return null;
}
Также необходимо добавить проверку, является переменная строковая или равно null. Добавить код необходимо перед закрывающейся скобкой цикла. Сразу после кода, приведенного выше.
// Special cases:
//  * If the constant value is a string, the type of the local declaration
//    must be System.String.
//  * If the constant value is null, the type of the local declaration must
//    be a reference type.
if (constantValue.Value is string)
{
    if (variableType.SpecialType != SpecialType.System_String)
    {
        return null;
    }
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
    return null;
}
Полный код метода приведен ниже.
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
{
    var localDeclaration = (LocalDeclarationStatementSyntax)node;
    // Only consider local variable declarations that aren't already const.
    if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
    {
        return null;
    }

    var semanticModel = document.GetSemanticModel(cancellationToken);

    var variableTypeName = localDeclaration.Declaration.Type;
    var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

    // Ensure that all variables in the local declaration have initializers that
    // are assigned with constant values.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var initializer = variable.Initializer;
        if (initializer == null)
        {
            return null;
        }

        var constantValue = semanticModel.GetConstantValue(initializer.Value);
        if (!constantValue.HasValue)
        {
            return null;
        }

        // Ensure that the initializer value can be converted to the type of the
        // local declaration without a user-defined conversion.
        var conversion = semanticModel.ClassifyConversion(initializer.Value, variableType);
        if (!conversion.Exists || conversion.IsUserDefined)
        {
            return null;
        }

        // Special cases:
        //  * If the constant value is a string, the type of the local declaration
        //    must be System.String.
        //  * If the constant value is null, the type of the local declaration must
        //    be a reference type.
        if (constantValue.Value is string)
        {
            if (variableType.SpecialType != SpecialType.System_String)
            {
                return null;
            }
        }
        else if (variableType.IsReferenceType && constantValue.Value != null)
        {
            return null;
        }

    }

    // Perform data flow analysis on the local declaration.
    var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration);

    // Retrieve the local symbol for each variable in the local declaration
    // and ensure that it is not written outside of the data flow analysis region.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var variableSymbol = semanticModel.GetDeclaredSymbol(variable);
        if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
        {
            return null;
        }

               

    }

    return new[]
    {
        new CodeIssue(CodeIssueKind.Warning, localDeclaration.Span,
            "Can be made constant",
            new CodeAction(document, localDeclaration))
    };
}
Осталось только исправить третью ошибку с использованием ключевого слова var. Для этого нам нужно перейти в CodeAction.cs и заметь код под комментарием "Produce the new local declaration" на код, приведенный ниже.
// If the type of the declaration is 'var', create a new type name
// for the inferred type.
var variableDeclaration = localDeclaration.Declaration;
var variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{

}

// Produce the new local declaration.
var newLocal = trimmedLocal.WithModifiers(newModifiers)
                            .WithDeclaration(variableDeclaration);
Затем необходимо добавить проверку в фигурных скобках блока if, чтобы убедиться, что тип переменной не является псевдонимом. Если это псевдоним другого типа (например, "using var = System.String;"), то такое поведение  является законным, чтобы объявить локальную "const var".
var semanticModel = document.GetSemanticModel();

// Special case: Ensure that 'var' isn't actually an alias to another type
// (e.g. using var = System.String).
var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
if (aliasInfo == null)
{

}
Внутри фигурных скобок необходимо написать проверку на извлечения типа "var".
// Retrieve the type inferred for var.
var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

// Special case: Ensure that 'var' isn't actually a type named 'var'.
if (type.Name != "var")
{

}
Теперь добавьте код, чтобы создать новый TypeSyntax для предполагаемого типа внутри фигурных скобок, блока if, который Вы написали выше.
// Create a new TypeSyntax for the inferred type. Be careful
// to keep any leading and trailing trivia from the var keyword.
var typeName = Syntax.ParseTypeName(type.ToDisplayString())
    .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
    .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
Добавить Simplify синтаксис аннотации, для того чтобы упростить имя типа, чтобы действие зашитое в CodeAction  уменьшало имя типа к его минимальной форме.
// Add an annotation to simplify the type name.
var simplifiedTypeName = CodeAnnotations.Simplify
    .AddAnnotationTo(typeName);
И, в самом конце, заменим тип объявления переменной тем, который только что создали.
Полный код функции GetEdit:
public CodeActionEdit GetEdit(CancellationToken cancellationToken = new CancellationToken())
{
    // Remove the leading trivia from the local declaration.
    var firstToken = localDeclaration.GetFirstToken();
    var leadingTrivia = firstToken.LeadingTrivia;
    var trimmedLocal = localDeclaration.ReplaceToken(
        firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

    // Create a const token with the original leading trivia.
    var constToken = Syntax.Token(leadingTrivia, SyntaxKind.ConstKeyword);

    // Insert the const token into the modifiers list, creating a new modifiers list.
    var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);

    // If the type of the declaration is 'var', create a new type name
    // for the inferred type.
    var variableDeclaration = localDeclaration.Declaration;
    var variableTypeName = variableDeclaration.Type;
    if (variableTypeName.IsVar)
    {
        var semanticModel = document.GetSemanticModel();

        // Special case: Ensure that 'var' isn't actually an alias to another type
        // (e.g. using var = System.String).
        var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
        if (aliasInfo == null)
        {
            // Retrieve the type inferred for var.
            var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

            // Special case: Ensure that 'var' isn't actually a type named 'var'.
            if (type.Name != "var")
            {
                // Create a new TypeSyntax for the inferred type. Be careful
                // to keep any leading and trailing trivia from the var keyword.
                var typeName = Syntax.ParseTypeName(type.ToDisplayString())
                    .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
                    .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

                // Add an annotation to simplify the type name.
                var simplifiedTypeName = CodeAnnotations.Simplify
                    .AddAnnotationTo(typeName);

                // Replace the type in the variable declaration.
                variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
            }

        }

    }

    // Produce the new local declaration.
    var newLocal = trimmedLocal.WithModifiers(newModifiers)
                                .WithDeclaration(variableDeclaration);

    // Add an annotation to format the new local declaration.
    var formattedLocal = CodeAnnotations.Formatting.AddAnnotationTo(newLocal);

    // Replace the old local declaration with the new local declaration.
    var oldRoot = document.GetSyntaxRoot(cancellationToken);
    var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

    // Create and return a new CodeActionEdit for the transformed tree.
    return new CodeActionEdit(document.UpdateSyntaxRoot(newRoot));
}
Давайте запустим созданный нами пример и посмотрим на исправленные ошибки.

Итоги

В приведенной статье мы создали свой extension для Visual Studio для работы с константами, а также рассмотрели возможности, которые нам предоставляет Roslyn. Надеюсь, на этом наше взаимодействие с проектом Roslyn не закончится. В следующей статье составим уже свой пример. Например, замена проверки строк на пустоту и null. Замена для IEnumerable коллекций проверки if(seq == null && seq.Count == 0) на seq.Any(), или что-то с более сложным уклоном. Обещаю над этим хорошенько подумать и преподнести новую статью по Roslyn. До новых встреч.