Entertaining C #. Five examples for a coffee break
Having written more than one article about Veeam Academy , we decided to open up a bit of internal cuisine and offer you a few examples in C # that we parse with our students. When compiling them, we made a start from the fact that our audience is novice developers, but it may also be interesting for experienced programmers to look under the cat. Our goal is to show how deep the rabbit hole is, while at the same time explaining the features of the internal structure of C #.
On the other hand, we will be glad to comment from experienced colleagues who either point out the shortcomings in our examples, or can share their own. Like questions like to use in interviews, so surely we all have something to tell.
We hope that our selection will be useful for you, help you refresh your knowledge or just smile.

Structures in C #. They often have questions even for experienced developers, which is what so many different online tests like to use.
Our first example is an example of attentiveness and knowledge of what the block is using. And also quite a topic for communication during the interview.
Consider the code:
Constructors and the sequence of their calls is one of the main themes of any object-oriented programming language. Sometimes such a sequence of calls can surprise and, much worse, even “fill up” the program at the most unexpected moment.
So, consider the MyLogger class:
Suppose that this class has some business logic that we need to support logging (functionality is not so important now).
Let's see what is in our class MyLogger:
For ease of analyzing this example inside the class constructors, we added a simple console output.
Outside the class (without using tricks like reflection) we can only use the open static Instance property, which we can call as follows:

Programmers often have to write helper functions (utilities, helpers, etc.) to make their lives easier. Usually such functions are fairly simple and often take up only a few lines of code. But you can even stumble out of the blue.
Suppose we need to implement such a function that checks a number for oddness (that is, that a number is not divisible by 2 without a remainder).
The implementation might look like this:
At first glance, everything is fine and, for example, for the numbers 5.7 and 11, we expectedly get True.
Anyone who programmed in C # probably met with LINQ, which is so convenient to work with collections, creating queries, filtering and aggregating data ...
We will not look under the hood of LINQ. Perhaps we will do it another time.
In the meantime, consider a small example:
Let's say at once - we do not consider this example at our lectures at the Academy, but sometimes we discuss it during coffee breaks more like an anecdote.
Despite the fact that it is unlikely to be indicative from the point of view of determining the level of the developer, this example was encountered in several different tests. Perhaps it is used for universality, because it works the same way both in C and C ++, and in C # and Java.
So let there be a line of code:

We are now recruiting for spring intensive C # in St. Petersburg, and we invite everyone to take an online test on the Veeam Academy website .
The course starts on February 18, 2019, will run until mid-May and will be, as always, completely free. Registration for anyone who wants to pass entrance testing is already available on the Academy website: academy.veeam.ru

On the other hand, we will be glad to comment from experienced colleagues who either point out the shortcomings in our examples, or can share their own. Like questions like to use in interviews, so surely we all have something to tell.
We hope that our selection will be useful for you, help you refresh your knowledge or just smile.

Example 1
Structures in C #. They often have questions even for experienced developers, which is what so many different online tests like to use.
Our first example is an example of attentiveness and knowledge of what the block is using. And also quite a topic for communication during the interview.
Consider the code:
publicstruct SDummy : IDisposable
{
privatebool _dispose;
publicvoidDispose()
{
_dispose = true;
}
publicboolGetDispose()
{
return _dispose;
}
staticvoidMain(string[] args)
{
var d = new SDummy();
using (d)
{
Console.WriteLine(d.GetDispose());
}
Console.WriteLine(d.GetDispose());
}
}
What will the Main method bring to the console?
Обратим внимание, что SDummy – это структура, реализующая интерфейс IDisposable, благодаря чему переменные типа SDummy можно использовать в блоке using.
Согласно спецификации языка C# using statement для значимых типов во время компиляции разворачивается в try-finally блок:
Итак, в нашем коде внутри блока using вызывается метод GetDispose(), который возвращает булевское поле _dispose, значение которого для объекта d еще нигде не было задано (оно задается только в методе Dispose(), который еще не был вызван) и поэтому возвращается значение по умолчанию, равное False. Что дальше?
А дальше самое интересное.
Выполнение строки в блоке finally
в обычном случае приводит к упаковке (boxing). Это нетрудно увидеть, например, тут (справа вверху в Results сначала выберите C#, а потом IL):

В этом случае метод Dispose вызывается уже для другого объекта, а вовсе не для объекта d.
Запустим нашу программу и увидим, что программа действительно выводит на консоль «False False». Но все ли так просто? :)
На самом деле никакой УПАКОВКИ НЕ ПРОИСХОДИТ. Что, по словам Эрика Липперта, сделано ради оптимизации (см. тут и тут).
Но, если нет упаковки (что само по себе может показаться удивительным), почему на экране «False False», а не «False True», ведь Dispose теперь должен применяться к тому же объекту?!?
А вот и не к тому же!
Заглянем, во что разворачивает нашу программу компилятор C#:
Появилась новая переменная sDummy2, к которой применяется метод Dispose()!
Откуда взялась эта скрытая переменная?
Снова обратимся к спеке:
Т.о. переменная sDummy оказывается невидимой и недоступной для встроенного выражения (embedded statement) блока using, а все операции внутри этого выражения производятся с другой переменной sDummy2.
В итоге метод Main выводит на консоль «False False», а не «False True», как считают многие из тех, кто столкнулся с этим примером впервые. При этом обязательно имейте в виду, что тут не происходит упаковки, но создается дополнительная скрытая переменная.
Общий вывод такой: изменяемые значимые типы (mutable value types) – это зло, которое лучше избегать.
Похожий пример рассмотрен тут. Если тема интересна, очень рекомендуем заглянуть.
Хочется сказать отдельное спасибо SergeyT за ценные замечания к этому примеру.
Согласно спецификации языка C# using statement для значимых типов во время компиляции разворачивается в try-finally блок:
try
{
Console.WriteLine(d.GetDispose());
}
finally
{
((IDisposable)d).Dispose();
}
Итак, в нашем коде внутри блока using вызывается метод GetDispose(), который возвращает булевское поле _dispose, значение которого для объекта d еще нигде не было задано (оно задается только в методе Dispose(), который еще не был вызван) и поэтому возвращается значение по умолчанию, равное False. Что дальше?
А дальше самое интересное.
Выполнение строки в блоке finally
((IDisposable)d).Dispose();
в обычном случае приводит к упаковке (boxing). Это нетрудно увидеть, например, тут (справа вверху в Results сначала выберите C#, а потом IL):

В этом случае метод Dispose вызывается уже для другого объекта, а вовсе не для объекта d.
Запустим нашу программу и увидим, что программа действительно выводит на консоль «False False». Но все ли так просто? :)
На самом деле никакой УПАКОВКИ НЕ ПРОИСХОДИТ. Что, по словам Эрика Липперта, сделано ради оптимизации (см. тут и тут).
Но, если нет упаковки (что само по себе может показаться удивительным), почему на экране «False False», а не «False True», ведь Dispose теперь должен применяться к тому же объекту?!?
А вот и не к тому же!
Заглянем, во что разворачивает нашу программу компилятор C#:
publicstruct SDummy : IDisposable
{
privatebool _dispose;
publicvoidDispose()
{
_dispose = true;
}
publicboolGetDispose()
{
return _dispose;
}
privatestaticvoidMain(string[] args)
{
SDummy sDummy = default(SDummy);
SDummy sDummy2 = sDummy;
try
{
Console.WriteLine(sDummy.GetDispose());
}
finally
{
((IDisposable)sDummy2).Dispose();
}
Console.WriteLine(sDummy.GetDispose());
}
}
Появилась новая переменная sDummy2, к которой применяется метод Dispose()!
Откуда взялась эта скрытая переменная?
Снова обратимся к спеке:
A using statement of the form 'using (expression) statement' has the same three possible expansions. In this case ResourceType is implicitly the compile-time type of the expression… The 'resource' variable is inaccessible in, and invisible to, the embedded statement.
Т.о. переменная sDummy оказывается невидимой и недоступной для встроенного выражения (embedded statement) блока using, а все операции внутри этого выражения производятся с другой переменной sDummy2.
В итоге метод Main выводит на консоль «False False», а не «False True», как считают многие из тех, кто столкнулся с этим примером впервые. При этом обязательно имейте в виду, что тут не происходит упаковки, но создается дополнительная скрытая переменная.
Общий вывод такой: изменяемые значимые типы (mutable value types) – это зло, которое лучше избегать.
Похожий пример рассмотрен тут. Если тема интересна, очень рекомендуем заглянуть.
Хочется сказать отдельное спасибо SergeyT за ценные замечания к этому примеру.
Example 2
Constructors and the sequence of their calls is one of the main themes of any object-oriented programming language. Sometimes such a sequence of calls can surprise and, much worse, even “fill up” the program at the most unexpected moment.
So, consider the MyLogger class:
classMyLogger
{
static MyLogger innerInstance = new MyLogger();
staticMyLogger()
{
Console.WriteLine("Static Logger Constructor");
}
privateMyLogger()
{
Console.WriteLine("Instance Logger Constructor");
}
publicstatic MyLogger Instance { get { return innerInstance; } }
}
Suppose that this class has some business logic that we need to support logging (functionality is not so important now).
Let's see what is in our class MyLogger:
- Static constructor set
- There is a private constructor without parameters
- Defined private static variable innerInstance
- And there is an open static property Instance for communication with the outside world.
For ease of analyzing this example inside the class constructors, we added a simple console output.
Outside the class (without using tricks like reflection) we can only use the open static Instance property, which we can call as follows:
classProgram
{
publicstaticvoidMain()
{
var logger = MyLogger.Instance;
}
}
What will this program display?
Все мы знаем, что статический конструктор вызывается перед доступом к любому члену класса (за исключением констант). При этом запускается он единственный раз в рамках application domain.
В нашем случае мы обращаемся к члену класса — свойству Instance, что должно вызвать сначала запуск статического конструктора, а потом вызовет конструктор экземпляра класса. Т.е. программа выведет:
Static Logger Constructor
Instance Logger Constructor
Однако после запуска программы мы получим на консоли:
Instance Logger Constructor
Static Logger Constructor
Как так? Инстанс конструктор отработал раньше статического конструктора?!?
Ответ: Да!
И вот почему.
В стандарте ECMA-334 языка C# на счет статических классов указано следующее:
17.4.5.1: «If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor.
…
17.11: … If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor
(Что в вольном переводе значит: если в классе есть статический конструктор, то запуск инициализации статических полей происходит непосредственно ПЕРЕД запуском статического конструктора.
…
Если класс содержит какие-либо статические поля с инициализаторами, то такие инициализаторы запускаются в порядке следования в тексте программы непосредственно ПЕРЕД запуском статического конструктора.)
В нашем случае статическое поле innerInstance объявлено вместе с инициализатором, в качестве которого выступает конструктор экземпляра класса. Согласно стандарту ECMA инициализатор должен быть вызван ПЕРЕД вызовом статического конструктора. Что и происходит в нашей программе: конструктор экземпляра, являясь инициализатором статического поля, вызывается ДО статического конструктора. Согласитесь, довольно неожиданно.
Обратим внимание, что это верно только для инициализаторов статических полей. В общем случае статический конструктор вызывается ПЕРЕД вызовом конструктора экземпляра класса.
Как, например, тут:
И программа ожидаемо выведет на консоль:
Static Logger Constructor
Instance Logger Constructor
В нашем случае мы обращаемся к члену класса — свойству Instance, что должно вызвать сначала запуск статического конструктора, а потом вызовет конструктор экземпляра класса. Т.е. программа выведет:
Static Logger Constructor
Instance Logger Constructor
Однако после запуска программы мы получим на консоли:
Instance Logger Constructor
Static Logger Constructor
Как так? Инстанс конструктор отработал раньше статического конструктора?!?
Ответ: Да!
И вот почему.
В стандарте ECMA-334 языка C# на счет статических классов указано следующее:
17.4.5.1: «If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor.
…
17.11: … If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor
(Что в вольном переводе значит: если в классе есть статический конструктор, то запуск инициализации статических полей происходит непосредственно ПЕРЕД запуском статического конструктора.
…
Если класс содержит какие-либо статические поля с инициализаторами, то такие инициализаторы запускаются в порядке следования в тексте программы непосредственно ПЕРЕД запуском статического конструктора.)
В нашем случае статическое поле innerInstance объявлено вместе с инициализатором, в качестве которого выступает конструктор экземпляра класса. Согласно стандарту ECMA инициализатор должен быть вызван ПЕРЕД вызовом статического конструктора. Что и происходит в нашей программе: конструктор экземпляра, являясь инициализатором статического поля, вызывается ДО статического конструктора. Согласитесь, довольно неожиданно.
Обратим внимание, что это верно только для инициализаторов статических полей. В общем случае статический конструктор вызывается ПЕРЕД вызовом конструктора экземпляра класса.
Как, например, тут:
classMyLogger
{
staticMyLogger()
{
Console.WriteLine("Static Logger Constructor");
}
publicMyLogger()
{
Console.WriteLine("Instance Logger Constructor");
}
}
classProgram
{
publicstaticvoidMain()
{
var logger = new MyLogger();
}
}
И программа ожидаемо выведет на консоль:
Static Logger Constructor
Instance Logger Constructor

Example 3
Programmers often have to write helper functions (utilities, helpers, etc.) to make their lives easier. Usually such functions are fairly simple and often take up only a few lines of code. But you can even stumble out of the blue.
Suppose we need to implement such a function that checks a number for oddness (that is, that a number is not divisible by 2 without a remainder).
The implementation might look like this:
staticboolisOddNumber(int i)
{
return (i % 2 == 1);
}
At first glance, everything is fine and, for example, for the numbers 5.7 and 11, we expectedly get True.
And what does the isOddNumber (-5) function return?
-5 нечетное число, но в качестве ответа нашей функции мы получим False!
Разберемся, в чем причина.
Согласно MSDN про оператор остатка от деления % написано следующее:
«Для целочисленных операндов результатом a % b является значение, произведенное a — (a / b) * b»
В нашем случае для a=-5, b=2 мы получаем:
-5 % 2 = (-5) — ((-5) / 2) * 2 = -5 + 4 = -1
Но -1 всегда не равно 1, что объясняет наш результат False.
Оператор % чувствителен к знаку операндов. Поэтому, чтобы не получать таких «сюрпризов», лучше результат сравнивать с нулем, у которого нет знака:
Или завести отдельную функцию проверки на четность и реализовать логику через нее:
Разберемся, в чем причина.
Согласно MSDN про оператор остатка от деления % написано следующее:
«Для целочисленных операндов результатом a % b является значение, произведенное a — (a / b) * b»
В нашем случае для a=-5, b=2 мы получаем:
-5 % 2 = (-5) — ((-5) / 2) * 2 = -5 + 4 = -1
Но -1 всегда не равно 1, что объясняет наш результат False.
Оператор % чувствителен к знаку операндов. Поэтому, чтобы не получать таких «сюрпризов», лучше результат сравнивать с нулем, у которого нет знака:
staticboolisOddNumber(int i)
{
return (i % 2 != 0);
}
Или завести отдельную функцию проверки на четность и реализовать логику через нее:
staticboolisEvenNumber(int i)
{
return (i % 2 == 0);
}
staticboolisOddNumber(int i)
{
return !isEvenNumber(i);
}
Example 4
Anyone who programmed in C # probably met with LINQ, which is so convenient to work with collections, creating queries, filtering and aggregating data ...
We will not look under the hood of LINQ. Perhaps we will do it another time.
In the meantime, consider a small example:
int[] dataArray = newint[] { 0, 1, 2, 3, 4, 5 };
int summResult = 0;
var selectedData = dataArray.Select(
x =>
{
summResult += x;
return x;
});
Console.WriteLine(summResult);
What will this code output?
Мы получим на экране значение переменной summResult, которое равно начальному значению, т.е. 0.
Почему так произошло?
А потому, что определение LINQ запроса и запуск этого запроса – это две операции, которые выполняются отдельно. Таким образом, определение запроса еще не означает его запуск/выполнение.
Переменная summResult используется внутри анонимного делегата в методе Select: последовательно перебираются элементы массива dataArray и прибавляются к переменной summResult.
Можно предположить, что наш код напечатает сумму элементов массива dataArray. Но LINQ работает не так.
Рассмотрим переменную selectedData. Ключевое слово var – это «синтаксический сахар», который во многих случаях сокращает размер кода программы и улучшает ее читабельность. А настоящий тип переменной selectedData реализует интерфейс IEnumerable. Т.е. наш код выглядит так:
Здесь мы определяем запрос (Query), но сам запрос не запускаем. Схожим образом можно работать с базой данных, задавая SQL запрос в виде строки, но для получения результата обращаясь к базе данных и запуская этот запрос в явном виде.
То есть пока мы только задали запрос, но не запустили его. Вот почему значение переменной summResult осталось без изменений. А запустить запрос можно, например, с помощью методов ToArray, ToList или ToDictionary:
Этот код уже выведет на экран значение переменной summResult, равное сумме всех элементов массива dataArray, равное 15.
С этим разобрались. А что тогда выведет на экран эта программа?
Переменная groupedData (строка 3) на самом деле реализует интерфейс IEnumerable и по сути определяет запрос к источнику данных dataArray. А это значит, что для работы анонимного делегата, который изменяет значение переменной summResult, этот запрос должен быть запущен явно. Но такого запуска в нашей программе нет. Поэтому значение переменной summResult будет изменено только в строке 2, а все остальное мы можем не рассматривать в наших вычислениях.
Тогда нетрудно посчитать значение переменной summResult, которое равно, соответственно, 15 + 7, т.е. 22.
Почему так произошло?
А потому, что определение LINQ запроса и запуск этого запроса – это две операции, которые выполняются отдельно. Таким образом, определение запроса еще не означает его запуск/выполнение.
Переменная summResult используется внутри анонимного делегата в методе Select: последовательно перебираются элементы массива dataArray и прибавляются к переменной summResult.
Можно предположить, что наш код напечатает сумму элементов массива dataArray. Но LINQ работает не так.
Рассмотрим переменную selectedData. Ключевое слово var – это «синтаксический сахар», который во многих случаях сокращает размер кода программы и улучшает ее читабельность. А настоящий тип переменной selectedData реализует интерфейс IEnumerable. Т.е. наш код выглядит так:
IEnumerable<int> selectedData = dataArray.Select(
x =>
{
summResult += x;
return x;
});
Здесь мы определяем запрос (Query), но сам запрос не запускаем. Схожим образом можно работать с базой данных, задавая SQL запрос в виде строки, но для получения результата обращаясь к базе данных и запуская этот запрос в явном виде.
То есть пока мы только задали запрос, но не запустили его. Вот почему значение переменной summResult осталось без изменений. А запустить запрос можно, например, с помощью методов ToArray, ToList или ToDictionary:
int[] dataArray = newint[] { 0, 1, 2, 3, 4, 5 };
int summResult = 0;
// определяем запрос и сохраняем его в переменной selectedData
IEnumerable<int> selectedData = dataArray.Select(
x =>
{
summResult += x;
return x;
});
// запускаем запрос selectedData
selectedData.ToArray();
// печатаем значение переменной summResult
Console.WriteLine(summResult);
Этот код уже выведет на экран значение переменной summResult, равное сумме всех элементов массива dataArray, равное 15.
С этим разобрались. А что тогда выведет на экран эта программа?
int[] dataArray = newint[] { 0, 1, 2, 3, 4, 5 }; //1var summResult = dataArray.Sum() + dataArray.Skip(3).Take(2).Sum(); //2var groupedData = dataArray.GroupBy(x => x).Select( //3
x =>
{
summResult += x.Key;
return x.Key;
});
Console.WriteLine(summResult); //4
Переменная groupedData (строка 3) на самом деле реализует интерфейс IEnumerable и по сути определяет запрос к источнику данных dataArray. А это значит, что для работы анонимного делегата, который изменяет значение переменной summResult, этот запрос должен быть запущен явно. Но такого запуска в нашей программе нет. Поэтому значение переменной summResult будет изменено только в строке 2, а все остальное мы можем не рассматривать в наших вычислениях.
Тогда нетрудно посчитать значение переменной summResult, которое равно, соответственно, 15 + 7, т.е. 22.
Example 5
Let's say at once - we do not consider this example at our lectures at the Academy, but sometimes we discuss it during coffee breaks more like an anecdote.
Despite the fact that it is unlikely to be indicative from the point of view of determining the level of the developer, this example was encountered in several different tests. Perhaps it is used for universality, because it works the same way both in C and C ++, and in C # and Java.
So let there be a line of code:
int i = (int)+(char)-(int)+(long)-1;
What is the value of the variable i?
Ответ: 1
Можно подумать, что здесь используется численная арифметика над размерами каждого типа в байтах, поскольку для преобразования типов тут довольно неожиданно встречаются знаки «+» и «-».
Известно, что в C# тип integer имеет размер 4 байта, long – 8, char – 2.
Тогда легко подумать, что наша строка кода будет равносильна следующему арифметическому выражению:
Однако это не так. А чтобы сбить с толку и направить по такому ложному рассуждению, пример может быть изменен, например, так:
Знаки «+» и «-» используются в этом примере не как бинарные арифметические операции, а как унарные операторы. Тогда наша строка кода является лишь последовательностью явных преобразований типов, перемешанных с вызовами унарных операций, которую можно записать так:
Можно подумать, что здесь используется численная арифметика над размерами каждого типа в байтах, поскольку для преобразования типов тут довольно неожиданно встречаются знаки «+» и «-».
Известно, что в C# тип integer имеет размер 4 байта, long – 8, char – 2.
Тогда легко подумать, что наша строка кода будет равносильна следующему арифметическому выражению:
int i = (4)+(2)-(4)+(8)-1;
Однако это не так. А чтобы сбить с толку и направить по такому ложному рассуждению, пример может быть изменен, например, так:
int i = (int)+(char)-(int)+(long)-sizeof(int);
Знаки «+» и «-» используются в этом примере не как бинарные арифметические операции, а как унарные операторы. Тогда наша строка кода является лишь последовательностью явных преобразований типов, перемешанных с вызовами унарных операций, которую можно записать так:
int i =
(int)( // call explicit operator int(char), i.e. char to int
+( // call unary operator +
(char)( // call explicit operator char(int), i.e. int to char
-( // call unary operator -
(int)( // call explicit operator int(long), i.e. long to int
+( // call unary operator +
(long)( // call explicit operator long(int), i.e. int to long-1
)
)
)
)
)
)
);

Interested in Studying at Veeam Academy?
We are now recruiting for spring intensive C # in St. Petersburg, and we invite everyone to take an online test on the Veeam Academy website .
The course starts on February 18, 2019, will run until mid-May and will be, as always, completely free. Registration for anyone who wants to pass entrance testing is already available on the Academy website: academy.veeam.ru
