📜 ⬆️ ⬇️

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 welcome comments 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.

image

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:

public struct SDummy : IDisposable { private bool _dispose; public void Dispose() { _dispose = true; } public bool GetDispose() { return _dispose; } static void Main(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?
Note that SDummy is a structure that implements the IDisposable interface, so that variables of type SDummy can be used in a using block.

According to the C # language specification, the statement for meaningful types is expanded during compilation into a try-finally block:

  try { Console.WriteLine(d.GetDispose()); } finally { ((IDisposable)d).Dispose(); } 

So, in our code, the GetDispose () method is called inside the using block, which returns a _dispose boolean field whose value for the object d has not been set anywhere (it is specified only in the Dispose () method that has not yet been called) and therefore returns default equal to False. What's next?

And then the most interesting.
Execute string in finally block
  ((IDisposable)d).Dispose(); 

in the usual case leads to boxing. It is easy to see, for example, here (at the top right in the Results, first select C # and then IL):

image

In this case, the Dispose method is already called for another object, and not at all for the object d.
Run our program and see that the program really displays “False False” on the console. But is everything so simple? :)

In fact, no packing is happening. What, according to Eric Lippert, is done for the sake of optimization (see here and here ).
But, if there is no packaging (which in itself may seem surprising), why on the screen “False False”, and not “False True”, because Dispose should now be applied to the same object?!?

And that's not the same thing!
Let's look at what our C # compiler deploys to our program:

 public struct SDummy : IDisposable { private bool _dispose; public void Dispose() { _dispose = true; } public bool GetDispose() { return _dispose; } private static void Main(string[] args) { SDummy sDummy = default(SDummy); SDummy sDummy2 = sDummy; try { Console.WriteLine(sDummy.GetDispose()); } finally { ((IDisposable)sDummy2).Dispose(); } Console.WriteLine(sDummy.GetDispose()); } } 


A new sDummy2 variable has appeared, to which the Dispose () method is applied!
Where did this hidden variable come from?
Again we turn to the speculation :
A using the statement of the form of using the expression has the same three possible expansions. In this case, the compile-time type of the expression is ...

So The sDummy variable is invisible and inaccessible for the embedded statement of the using block, and all operations inside this expression are performed with another sDummy2 variable.

As a result, the Main method displays “False False” rather than “False True” on the console, as many of those who have encountered this example for the first time consider. In this case, be sure to keep in mind that packaging does not occur here, but an additional hidden variable is created.

The general conclusion is this: mutable value types are evil that should be avoided.

A similar example is considered here . If the topic is interesting, we highly recommend to take a look.

I would like to say a special thanks to SergeyT for valuable comments on this example.



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:

 class MyLogger { static MyLogger innerInstance = new MyLogger(); static MyLogger() { Console.WriteLine("Static Logger Constructor"); } private MyLogger() { Console.WriteLine("Instance Logger Constructor"); } public static 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:

  1. Static constructor set
  2. There is a private constructor without parameters
  3. Defined private static variable innerInstance
  4. 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:

 class Program { public static void Main() { var logger = MyLogger.Instance; } } 

What will this program display?
We all know that the static constructor is called before accessing any member of the class (except for constants). At the same time, it runs only once within the application domain.

In our case, we are accessing a class member — the Instance property — which should first trigger the static constructor, and then invoke the instance constructor of the class. Those. the program will output:

Static Logger Constructor
Instance Logger Constructor


However, after launching the program we will get on the console:

Instance Logger Constructor
Static Logger Constructor


How so? Instance constructor worked before the static constructor?!?
Answer: Yes!

And that's why.

In the ECMA-334 standard of C #, the following is indicated on the static classes account:

17.4.5.1: “If a static constructor (§17.11) exists, the initializers occur immediately after the session.
...
11/17: ...

(What means in a free translation: if there is a static constructor in the class, then the initialization of the static fields starts immediately BEFORE the static constructor is started.
...
If the class contains any static fields with initializers, then such initializers are started in the following order in the program text BEFORE launching the static constructor.)

In our case, the static field innerInstance is declared together with the initializer, which is the class instance constructor. According to the ECMA standard, the initializer must be called BEFORE calling the static constructor. What happens in our program: the instance constructor, being the initializer of a static field, is called BEFORE the static constructor. Agree, quite unexpectedly.

Note that this is only true for static field initializers. In general, a static constructor is called BEFORE a call to the instance constructor of a class.

As, for example, here:

 class MyLogger { static MyLogger() { Console.WriteLine("Static Logger Constructor"); } public MyLogger() { Console.WriteLine("Instance Logger Constructor"); } } class Program { public static void Main() { var logger = new MyLogger(); } } 

And the program is expected to display on the console:

Static Logger Constructor
Instance Logger Constructor


image

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:

 static bool isOddNumber(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 is an odd number, but in response to our function, we get False!
We will understand what is the reason.

According to MSDN , the following is written about the operator of the remainder of division%:
"For integer operands, the result of a% b is the value produced by a - (a / b) * b"
In our case, for a = -5, b = 2 we get:
-5% 2 = (-5) - ((-5) / 2) * 2 = -5 + 4 = -1
But -1 is not always equal to 1, which explains our result False.

The% operator is sensitive to the sign of the operands. Therefore, in order not to receive such "surprises", it is better to compare the result with zero, which has no sign:

 static bool isOddNumber(int i) { return (i % 2 != 0); } 

Or create a separate parity function and implement logic through it:

 static bool isEvenNumber(int i) { return (i % 2 == 0); } static bool isOddNumber(int i) { return !isEvenNumber(i); } 


Example 4


Anyone who has programmed in C # has probably met LINQ, which is so convenient to work with collections, creating queries, filtering and aggregating data ...

We will not look under the hood LINQ. Perhaps we will do it another time.

In the meantime, consider a small example:

 int[] dataArray = new int[] { 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?
We will get on the screen the value of the variable summResult, which is equal to the initial value, i.e. 0

Why did it happen?

Because the definition of a LINQ query and the launch of this query are two operations that are performed separately. Thus, the definition of a query does not mean its start / execution.

The variable summResult is used inside the anonymous delegate in the Select method: the elements of the dataArray array are sequentially iterated and added to the summResult variable.

We can assume that our code will print the sum of the elements of the dataArray array. But LINQ doesn't work that way.

Consider the variable selectedData. The var keyword is “syntactic sugar”, which in many cases reduces the size of the program code and improves its readability. And the actual type of the selectedData variable implements the IEnumerable interface. Those. our code looks like this:

  IEnumerable<int> selectedData = dataArray.Select( x => { summResult += x; return x; }); 

Here we define the query (Query), but the query itself does not run. Similarly, you can work with the database by setting the SQL query as a string, but to get the result, refer to the database and run this query explicitly.

That is, for the time being, we only asked for the request, but did not launch it. That is why the value of the summResult variable remains unchanged. And you can run a query, for example, using the ToArray, ToList or ToDictionary methods:

 int[] dataArray = new int[] { 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); 

This code will already display the value of the variable summResult, equal to the sum of all elements of the dataArray array, equal to 15.

With this sorted out. And what then will this program display?

 int[] dataArray = new int[] { 0, 1, 2, 3, 4, 5 }; //1 var summResult = dataArray.Sum() + dataArray.Skip(3).Take(2).Sum(); //2 var groupedData = dataArray.GroupBy(x => x).Select( //3 x => { summResult += x.Key; return x.Key; }); Console.WriteLine(summResult); //4 

The variable groupedData (line 3) actually implements the IEnumerable interface and essentially defines the query to the dataArray data source. This means that for an anonymous delegate to work, which changes the value of the summResult variable, this query must be run explicitly. But there is no such launch in our program. Therefore, the value of the variable summResult will be changed only in line 2, and we can disregard everything else in our calculations.

Then it is easy to calculate the value of the variable summResult, which is, respectively, 15 + 7, i.e. 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?
Answer: 1

One might think that here numerical arithmetic is used over the sizes of each type in bytes, since the “+” and “-” signs are quite unexpectedly found for type conversion.

It is known that in C # the type integer has a size of 4 bytes, long - 8, char - 2.

Then it is easy to think that our line of code will be equivalent to the following arithmetic expression:

 int i = (4)+(2)-(4)+(8)-1; 

However, it is not. And in order to confuse and direct by such false reasoning, the example can be changed, for example, as follows:

 int i = (int)+(char)-(int)+(long)-sizeof(int); 

The signs “+” and “-” are used in this example not as binary arithmetic operations, but as unary operators. Then our line of code is only a sequence of explicit type conversions mixed with calls to unary operations, which can be written like this:

  int i = (int)( // call explicit operator int(char), ie char to int +( // call unary operator + (char)( // call explicit operator char(int), ie int to char -( // call unary operator - (int)( // call explicit operator int(long), ie long to int +( // call unary operator + (long)( // call explicit operator long(int), ie int to long -1 ) ) ) ) ) ) ); 


image

Interested in Studying at Veeam Academy?


Now there is a set 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

image

Source: https://habr.com/ru/post/439478/