Во время подготовки к экзамену номер 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:GACKeyGACkey.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
Что такое WinMD? WinMD — это по сути dll, но с возможностями cross platform. То есть можно создать WinMD и назначить платформы в которых она будет использоваться ( например: WP8, WinRT, WPF… ). По сути, добавление каждой платформы сужает возможности сборки
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 = @"bw+esb";
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#
Автор: asommer