Pull to refresh

Подготовка к сертификационному экзамену Microsoft 70-483 «Programming in C#»

Reading time 13 min
Views 103K

Во время подготовки к экзамену номер 70-483 нашел множество разрозненных сайтов с различными ссылками на мануалы, которые мне немного помогли. Но, что помогло мне больше, так это то, что я составил для себя памятку на нескольких страницах, выдержками из которой и хочу поделиться.
Целью не является подробное описание C#, целью является освежить в памяти и заострить внимание на некоторых необходимых темах. Если какие-то темы вам незнакомы, то это значит, что у вас есть пробелы, которые необходимо устранить.
Раскрывать вопросы тестирования я не могу (да и не помню я уже их), но, если многие из перечисленных ниже нюансов, трюков и тонкостей, помогут вам, то вы на меня не обижайтесь (написано с иронией).

Начнем с простых вещей, о которых часто забывают, но которые часто используют в тестах:
Для того, чтобы объявить переменную типа int, и присвоить ей сразу значение 0, можно сделать так: var myInt=0; или так int myInt=0;
Для того, чтобы объявить переменную типа int, и задать сразу ей значение по умолчанию, нужно сделать так: int myInt = new int();

Explicit and Implicit типизация
int i = 42; // это explicit типизация – указан тип переменной int
var i = 42;  // а это уже implicit типизация. Компилятор задает переменной тип исходя из ее значения.

Используйте implicit типизацию при:
Анонимных типах — var anonymousType = new { Name = «Alex» };
Получении результата запроса LINQ — var queryExpression = from c in customers where c.Name == «Andrey» select c;
Использовании complex generic типов — var searchList = new Dictionary();
Implicit типизация возможна только для локальных переменных.

Boxing/Unboxing
Boxing – это конвертация value типа в тип reference
Unboxing наоборот – конвертация reference типа в value. Для Unboxing-а необходим Cast.
Boxing/Unboxing копирует значение переменной.
Boxing с точки зрения скорости вычислений — операция занимающая довольно много процессорных ресурсов.
Простой пример:
int i = 123;
// Следующая строчка упаковывает переменную i (происходит boxing) 
object o = i;  // object – это тип reference тип, а int – это тип value
j = (int)o;  // unboxing – распаковка

Интересный пример ошибки:
double e = 2.718281828459045;
object o = e; // box
// int ee = (int)o; // вызовет runtime exception, так как o не может быть распознано как тип int
int ee = (int)(double)o; // сделает unboxing без ошибки

Еще пример:
int count=1;
object countObject=count; // значение count скопировано в переменную countObject – это boxing
count+=1; // count уже равен 2, а значение countObject все еще 1
count=(int)countObject; // теперь и count равно 1 – это был unboxing

Оператор ?? называется null-coalescing operator. Он возвращает левое значение, в случае, если оно не равно null, в ином случае правое значение. Пример:
int? x = null; // здесь int? означает что это Nullable type т.е. что можно объекту присвоить null
int y = x ?? -1; // В этом случае y равен -1 потому что x равен null

Оператор ?: называется conditional operator. Он возвращает одно из двух значений исходя из булева выражения. Если выражение равно true, то возвращается первое значение, в ином случае — второе. Для примера 2 аналогичных кода:
if (input<0)  // используя конструкцию if-else
    classify = "negative";
else
    classify = "positive";

classify = (input < 0) ? "negative" : "positive";  // используя conditional operator ?:

LINQ
Любой объект, который реализует интерфейс IEnumerable или IQueryable может быть запрошен используя LINQ.
Можно использовать как orderby … descending так и OrderByDescending
orderby h.State, h.City – здесь orderby пишется слитно, однако group h by h.State раздельно ( есть вариант записи заглавными буквами GroupBy )
Predicate – это условие, которое характеризует субъект. Например:
 where i % 2 == 0

Есть 2 варианта записи LINQ ( синтаксис метода/method и синтаксис запроса/query ):
var data=Enumerable.Range(1,100);
var method=data.Where(x=>x%2==0).Select(x=>x.ToString());
var query=from d in data where d%2==0 select d.ToString();

Простые методы LINQ:
var values=new[]{“A”,”B”,”C”,”B”,”B”,”A”,”B”};
var distinct=values.Distinct(); // только неуникальные значения
var first=values.First();
var firstordef=values.FirstOrDefault();
var twoval=values.Skip(2).Take(2);

Интересна возможность вместо && использовать where несколько раз. Например:
var evenNumbers = from i in myArray where i % 2 == 0 where i > 5 select i;

Можно использовать функции в качестве фильтра. Например:
var evenNumbers = from i in myArray where IsEvenAndGT5(i) select i;
static bool IsEvenAndGT5(int i)
{
return (i % 2 == 0 && i > 5);
}

Если необходимо выбрать несколько значений, то нужно использовать new { }
var names = from p in people select new { p.FirstName, p.LastName };

Причем, можно задать даже псевдонимы:
var names = from p in people select new { First = p.FirstName, Last = p.LastName };

var names = people.Select(p => new { p.FirstName, p.LastName }); // вариант запроса с синтаксисом метода

LINQ deferred execution — до тех пор, пока элемент не вызывается запрос не выполняется. Пример:
int[] myArray = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            var evenNumbers = from i in myArray where i % 2 == 0 select i;
            foreach (int i in evenNumbers)
            {
                Console.WriteLine(i); // выводим результат LINQ запроса
            }
            myArray[1] = 12; // изменяется элемент массива из которого происходит выборка запросом
            foreach (int i in evenNumbers) // здесь еще раз происходит вызов запроса LINQ
            {
                Console.WriteLine(i); // выводится первым элементом уже 12 а не 2
            }
// а вот следующий запрос это Immediate Execution. Данные извлекаются сразу
            var evenNumbersCount=(from i in myArray where i % 2 == 0 select i).Count();

Версия сборки состоит из четырех частей: Major, Minor, Build, and Revision

Для установки сборки в GAC (Global Assembly Cache) необходима утилита Global Assembly Cache tool (Gacutil.exe). В командной строке вводится команда: gacutil –i
Но необходимо, чтобы сборка была подписана ключом строгого имени с помощью средства SN
Пример создания ключа: «C:\GACKey\GACkey.snk» sn –k
Сборка со строгим именем (strong name) состоит из 5-ти частей:Friendly Name, Version, Culture, Public Key Token, and Processor Architecture
Подписывая Ваши сборки, храните ключ в надежном месте, а разработчикам выдайте открытую часть ключа, что позволит им использовать сборки без полной подписи.
Выделить открытую часть ключа можно следующей командой:
Sn.exe –p «ключ.snk» «открытый ключ.snk»

Так как версия сборки важна в тот момент, когда runtime пытается найти сборку, то вы можете публиковать различные версии одной и той же сборки в GAC и избежать проблем, которые случаются с обычными dll.
Это называется side-by-side hosting, при котором множество версий одной и той же сборки хостятся одновременно на одном компьютере (начиная с .Net 4).

Shallow copy — ловушка при копировании массивов
При копировании объектов ссылочного типа может быть скопирован указатель на значение, а не само значение. Пример:
Person[] orginal = new Person[1];
orginal[0] = new Person() { Name = "John" };
Person[] clone = (Person[])orginal.Clone();
clone[0].Name = "Mary";  
// что интересно – теперь и original[0] и clone[0] содержат “Mary” так как массив ссылочного типа

Person[] orginal = new Person[1];
orginal[0] = new Person() { Name = "John" };
Person[] clone = (Person[])orginal.Clone();
clone[0] = new Person() { Name = "Bob" }; 
// что интересно теперь original[0]=”John” а clone[0]=”Bob”
// т.к. ссылка на первый элемент массива original была заменена новым созданным элементом

Closures ( замыкания )
var funcs = new List<Func>();
for (int i = 0; i < 3; i++)
{
    funcs.Add(() => i);
}
foreach (var f in funcs)
    Console.WriteLine(f());  // Выведет на экран 3 3 3 а не 1 2 3 как ожидается…

Данный результат обусловлен двумя причинами:
1. при замыканиях осуществляется захват переменных, а не значений переменных и
2. в приведенном фрагменте кода, существует один экземпляр переменной i, который изменяется на каждой итерации цикла, а не создается новый экземпляр на каждой итерации.

Исправить данную ситуацию довольно просто:
var funcs = new List<Func>();
for (int i = 0; i < 3; ++i)
{
    int tmp = i;  // создаем временную переменную
    funcs.Add(() => tmp); 
// при добавлении в List происходит захват значения временной переменной
}
foreach (var f in funcs)
    Console.WriteLine(f()); // теперь все в порядке и мы получим 1, 2, 3

Вы директивы компилятора используете? Вот пара директив для примера:
#region и #endregion – ограничивают какую-то часть кода и разрешают сворачивание/разворачивание
#define и #undef – define задают true или false соответственно каким-либо значениям
#error – сгенерирует ошибку

Пример условия, которое в случае, если сборка в режиме DEBUG выполнит один кусок кода, а в ином случае другой:
#if DEBUG
#elif
#endif

Что такое PCL? Portable Component Library — это по сути dll, но с возможностями cross platform. То есть можно создать PCL и назначить платформы в которых она будет использоваться ( например: WP8, WinRT, Xamarin… ). По сути, добавление каждой платформы сужает возможности сборки

PDB это акроним для Program database file
PDB содержит отладочные данные и сведения о состоянии проекта, позволяющие выполнять последовательную компоновку отладочной конфигурации программы
Любой имеющий доступ к dll/exe файлу может легко произвести реверс инжиниринг для того, чтобы генерировать исходный код с PDB или без него, используя такие средства, как например, reflector. Так что, предоставление или не предоставление PDB никак не влияет не безопасность кода.

Implicit и Explicit реализации интерфейса
Допустим, что у нас есть 2 интерфейса с одинаковым методом:
interface InterfaceOne
{
    void InterfaceMethod();
}
interface InterfaceTwo
{
    void InterfaceMethod();
}

Мы создаем класс, наследуемый от двух интерфейсов и в нем объявляем метод interfaceMethod:
public class MyClass : InterfaceOne, InterfaceTwo
{
    public void InterfaceMethod()
    {
        Console.WriteLine("Угадай метод какого интерфейса вызван?");
    }
}

Сейчас мы использовали Implicit реализацию интерфейса.
А если мы укажем наименование интерфейса, то это будет Explicit реализация:
public class MyClass : InterfaceOne, InterfaceTwo
{
    void InterfaceOne.InterfaceMethod()
    {
   Console.WriteLine("Сейчас легко угадать какого это интерфейса метод, правда?");
    }
    void InterfaceTwo.InterfaceMethod()
    {
   Console.WriteLine("Сейчас легко угадать какого это интерфейса метод, правда?");
    }
}

В информатике отражение или рефлексия (синоним интроспекция, англ. reflection) означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.
Пример:
void Method()
{
	var horse=new Animal();
	var type=horse.GetType();
	var method=type.GetMethod(“Speak”);
	var value=(string)method.Invoke(horse,null);   // value=”hello”;
}
public class Animal
{
	public string Speak() { return “hello”; }
}

При рефлексии сборку можно загрузить различными способами:
Assembly.LoadFrom() может сделать перенаправление на другую аналогичную сборку
Assembly.LoadFile() загружает конкретно сборку из файла.
Assembly.Load() загружает сборку по указанию ее полного имени

Разница между Convert и Parse методами в том, что Parse принимает только строку как вводное значение, в то время, как Convert может принимать и другие типы данных.
Разница между Parse/TryParse и Convert в том, что Convert может принимать нулевые значения и не выбрасывает ArgumentNullException.

Для представления положительной и отрицательной бесконечности в C# имеется две специальные константы: System.Double.NegativeInfinity и System.Double.PositiveInfinity
NegativeInfinity — Значением этой константы является результат деления отрицательного числа на нуль, также Данная константа возвращается в случае, если результат операции меньше, чем минимальное значение (MinValue).
PositiveInfinity – результат деления положительного числа на 0. Константа возвращается в случае, если значение больше, чем MaxValue.
Infinity включает в себя как NegativeInfinity так и PositiveInfinity

Ключевое слово unchecked используется для подавления проверки переполнения при выполнении арифметических операций и преобразований с данными целого типа. Если среда unchecked удалена, возникает ошибка компиляции. 2 примера:
unchecked
{
    int1 = 2147483647 + 10;
}
int1 = unchecked(2147483647 + 10);

По умолчанию C# не выбрасывает исключение, если при сужающем/narrowing преобразовании возникает ошибка с int и float типами.
Для того, чтобы исключение возникало можно использовать ключевое слово checked. Пример:
checked
{
int big = 1000000;
short small = (short)big;
}

Также можно в свойствах проекта поставить галочку о возникновении исключения при overflow/underflow

Форматирование
Console.WriteLine(somevariable.ToString("c"))  // форматирует в денежный формат
// например указав somevariable.ToString("C3", new System.Globalization.CultureInfo("fr-FR")) в случае если somevariable=123.4562 получим -123,456 €
// ToString("d") или ToString("D") форматирует число в виде decimal
// можно указать число в виде минимального количества символов. Например, указав ("D6") получим из -1234 число -001234 
DateTime.Now.ToString("D")  
// в случае, если параметром является дата, форматирует в короткий формат даты = DateTime.Now.ToShortDateString()
DateTime.Now.ToString("d")  // длинный формат даты


Rethrow exception
Стек ошибки очищается каждый раз, когда выбрасывается ошибка.
static string ReadAFile(string fileName) {
    string result = string.Empty;
    try {
        result = File.ReadAllLines(fileName);
    } catch(Exception ex) {
        throw ex; // Это отобразит ReadAFile в StackTrace – не совсем верное решение
        throw;    // Это же отобразит ReadAllLines в StackTrace – лучше использовать такое
    }

Рекомендуется использовать просто throw, так как в таком случае сохранится информация об исходной ошибке.

Как вариант можно создать свою ошибку — custom exception
[Serializable()]
public class YourCustomException : System.Exception
{
   // конструктор принимает текст как параметр
   public YourCustomException(string msg) : base(msg)
   {

   }
}

И после можно выбрасывать ошибку:
try
{
throw new YourCustomException("Вам нельзя вводить текст в это поле");
// какой-то код
}
catch(YourCustomException e){}

Не следует наследовать от System.ApplicationException. В идее было, что все C# runtime ошибки должны наследоваться от System.Exception, а все custom exceptions от System.ApplicationException. Но в реалии класс System.ApplicationException не используется совсем.

В блока try/catch/finally — finally выполняется всегда. Только в случае вызова в коде Environment.FailFast(String) или FailFast(String, Exception), которые прерывают текущий процесс, в случае каких-либо критических повреждений в работе приложения, выполнение программы будет прервано и finally не будет выполнено.

CodeDOM – это набор классов, которые позволяют генерировать код.
Класс System.CodeDom.CodeCompileUnit – это класс верхнего топ-уровня, контейнер для всех объектов в генерируемом классе.
Класс System.CodeDom.CodeDOMProvider генерирует класс файла на C#, VB или JScript.

Структуры как и классы могут иметь методы, свойства, конструкторы и др.
Структуры в отличие от классов не могут содержать деструкторы ( фактически деструкторы это Finalize методы ~ИмяКласса которые позволяют уничтожить объект )
Нельзя объявить пустой конструктор для структуры.
Также структуры не могут иметь иерархии наследования (для экономии памяти). В C# структуры запечатаны — implicitly sealed. Что означает, что наследовать от структуры нельзя.
Структуры – это value тип данных = хранится в стеке.
Классы – это reference тип данных = хранится в куче ( heap ).
В C# к Value типу данных относятся структуры и перечисления (structs и enumerations).
Структуры в свою очередь делятся на подкатегории: числовой тип (целочисленный — integral types, с плавающей точкой — floating-point types, 128-и разрядный decimals), булев тип (bool) и пользовательские структуры.
Перечисления это набор типов, объявленный с помощью ключевого слова enum.
У value типа данных есть ограничения. Вы не можете наследовать от value типа, а также value тип не может содержать в себе значение null.
enum Months {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sept, Oct, Nov, Dec};
// можно использовать не int а другой числовой тип для enum
enum Months : byte {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sept, Oct, Nov, Dec};

string name = Enum.GetName(typeof(Months), 8);
Console.WriteLine("Восьмой месяц в enum это " + name);

Console.WriteLine("Числовые значения Months enum:");
foreach (int values in Enum.GetValues(typeof(Months)))
{
Console.WriteLine(values);
}

Класс Interlocked — Предоставляет атомарные операции для переменных, общедоступных нескольким потокам ( операции Decrement(Int), Increment(Int), Add(Int, Int) ) Значение переменной будет изменено одновременно для всех потоков.
Метод CompareExchange — сравнивает два значения на равенство и, если они равны, заменяет одно из значений.
Interlocked.CompareExchange(ref value, newvalue, comparevalue)

Метод Exchange — задает переменную указанным значением — как атомарная операция и возвращает исходное значение. Пример:
if (Interlocked.Exchange(ref isInUse, 1) ==0){
// какой-то код
 }

Параметры, передаваемые функциям
static int CalculateBMI(int weight, int height)
    {
        return (weight * 703) / (height * height);
    }

Стандартно функция вызывается так: CalculateBMI(123, 64);
Если не помнить порядок, то можно вызывать функцию, указывая названия:
CalculateBMI(weight: 123, height: 64);
CalculateBMI(height: 64, weight: 123);

Можно сперва указать параметр по порядку, а затем по имени
CalculateBMI(123, height: 64);

Но нельзя указывать сперва параметр по имени, а затем по порядку ( вызовет ошибку )
//CalculateBMI(weight: 123, 64);

Можно указывать значения для параметров по умолчанию и вызывать после функцию без указания этих параметров:
public void ExampleMethod(int required, string optionalstr = "default string", int optionalint = 10)

Использование StringWriter и StringReader для записи и чтения строк
	    StringWriter strwtr = new StringWriter();
	    for(int i=0; i < 10; i++)
	    strwtr.WriteLine("This is i: " + i);
	  	  
	    StringReader strrdr = new StringReader(strwtr.ToString());
	    // Сейчас читаем из StringReader.
	    string str = strrdr.ReadLine();
	    while(str != null) {
	    str = strrdr.ReadLine();
	    Console.WriteLine(str);

Сравнение Generics и ArrayList
var objects=new ArrayList();
objects.Add(1);
objects.Add(2);
objects.Add(“three”); // ArrayList принимает различные типы дынных

var list=new List<int>();
list.Add(1);
list.Add(2);
list.Add(“three”); 
// последнее непозволительно, так как generic коллекция в данном случае принимает только int тип данных

Regex ( регулярные выражения )
Пример IsMatch:
 if (Regex.IsMatch(phone, @"^\d{3}-\d{4}$"))

Matches — Ищет во входной строке все вхождения регулярного выражения и возвращает все соответствия:
      string pattern = @"\b\w+es\b";
      Regex rgx = new Regex(pattern);
      string sentence = "Who writes these notes?";
      foreach (Match match in rgx.Matches(sentence))
      Console.WriteLine("Found '{0}' at position {1}",  match.Value, match.Index);

Match – работает точно так же как и Matches, но ищет только первое совпадение
Split — Разделяет входную строку в массив подстрок в позициях, определенных соответствием регулярного выражения.
string input = «plum-pear»;
string pattern = "(-)";
string[] substrings = Regex.Split(input, pattern);

Replace — В указанной входной строке заменяет все строки, соответствующие шаблону регулярного выражения, указанной строкой замены.
^ — начало строки $ — конец строки. – какой-либо символ
* — повторения предыдущего 0 или несколько раз
+ — повторение предыдущего 1 или несколько раз
? – повторение предыдущего 0 или 1 раз
[abc] – какой-то из символов a,b или c [^abc] – какой-то из символов кроме этих [a-z] – символы от и до [^a-z] – символы кроме от и до
{n} – совпадение n повторений {n,} – совпадение от n до бесконечности {n,m} – совпадение от n и до m раз
\d – число \D – не число \s – пробел, таб и т.п. \S – не пробел

Немного «вредных» советов


Несмотря на запрет распространения информации об экзамене, при поиске в сети вполне можно найти дампы (сливы информации по вопросам) в частности и к этому экзамену. Стоит ли ими пользоваться и насколько сильно они могут помочь в сдаче экзамена? Даже судя по записям в блогах, пользуются ими многие.

На мой взгляд, зацикливаться на дампах не стоит. Например, потому что они содержат в себе множество ошибок. Также, следует учесть то, что вопросы время от времени вопросы заменяются. Можно ли смухлевать при сдаче? Пожалуй, немного повезти с вопросами может, но ведь для нас важна не бумажка, а владение языком, правда?
Довольно легальным способом найти примеры вопросов может быть ознакомление с вопросами из книг: «MCSD Certification Toolkit Exam 70-483 Programming in C#» и «Wouter de Kort Programming in C# Exam Ref 70-483». Книги на английском (как собственно и сам экзамен) и могут помочь вам прокачать свои знания технического английского. Я бы советовал, изучая различные вопросы, обращать внимание не на варианты ответа, а на саму тему вопроса. Таким образом, можно узнать какие-то нюансы языка, а также проработать неизвестные области.
Из видеокурсов могу посоветовать вам курс на MVA ( портал совершенно бесплатный )
www.microsoftvirtualacademy.com/training-courses/developer-training-with-programming-in-c
Довольно интересная, обучающая не только C#, но и при желании и Java онлайн игра находится по адресу www.codehunt.com В качестве подготавливающей к тестированию она не подойдет, но может быть интересна в качестве разминки.

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

На этом «вредные» советы заканчиваются. Это лишь только часть того что нужно знать и помнить.
Желаю всем высоких баллов, а также, конечно же, знаний и понимания языка C#
Tags:
Hubs:
+28
Comments 17
Comments Comments 17

Articles