📜 ⬆️ ⬇️

Entertaining C #

Entertaining C #

In order to assess the quality of diagnostics of the PVS-C code analyzer in PVS-Studio, we check a large number of different projects. Since projects are written by different people in different teams in different companies, we have to deal with different styles, abbreviations, and simply the possibilities that C # language offers to programmers. In this article I want to go over the overview on some points that the wonderful C # language offers us, and on the problems that you can stumble upon while using it.

Picture 1


Remark.
This article is more focused on curiosity and describes those things about which I personally found it interesting to tell.
')

Properties and what can be done with them


We all know that properties are a couple of functions: an accessor and a mutator, for changing and reading a value in some field. Well, or at least it was up to C # 3.0. Those. classically they should look like this:
class A { int index; public int Index { get { return index; } set { index = value; } } } 

Years passed, and the standards of the language and the properties were overgrown with various possibilities.

Let's start a little. In the standard C # 3.0, there was a well-known opportunity to omit a field, i.e. write this:
 class A { public int Index { get; set; } } 

In C # 6.0, we went even further and allowed us to remove the “set”.
 class A { public int Index { get; } } 

So it was possible to write up to C # 6.0, but it was impossible to write something into such a variable. Now this, in fact, is analogous to readonly fields, i.e. you can set the value of such properties only in the constructor.

Properties and fields can be initialized in various ways. For example:
 class A { public List<int> Numbers { get; } = new List<int>(); } 

Or like this:
 class A { public List<int> Numbers = new List<int>(); } 

And you can also write this:
 class A { public List<int> Numbers => new List<int>(); } 

And in the latter case, you will have to wait for an unpleasant surprise. In fact, in the last example you created the following property:
 class A { public List<int> Numbers { get { return new List<int>(); } } } 

Those. when you try to fill in Numbers , then you will fail in principle, each time you will have a new list.
 A a = new A(); a.Numbers.Add(10); a.Numbers.Add(20); a.Numbers.Add(30); 

Be careful when you shorten the record, sometimes it can lead to a very long search for errors.

The interesting properties of the properties do not end there. As I said, a property is a pair of functions, and in functions no one bothers to change the parameters that come there.

The following code compiles perfectly and even works.
 class A { int index; public int Index { get { return index; } set { value = 20; index = value; } } } static void Main(string[] args) { A a = new A(); a.Index = 10; Console.WriteLine(a.Index); } 

The result of the work will be the conclusion of the number "20", and not "10".

It would seem, why would someone give up writing down the value 20 in value ? It turns out that even this can be washed away. But to clarify this meaning, we digress a bit from the properties and tell you about the key symbol @. This key symbol allows you to create variables similar in writing with keywords. For example: @this , @operator , etc. But no one forbids this symbol to shove wherever the soul wishes, for example:
 class A { public int index; public void CopyIndex(A @this) { this.@index = @this.index; } } static void Main(string[] args) { A a = new A(); @a.@index = 10; a.@CopyIndex(new A() { @index = 20 }); Console.WriteLine(a.index); } 

The result of the work, as always in this article, will be the derivation of the number “20”, and in no way “10”.

In fact, the @ symbol is needed only in one place when we write the name of the @this parameter in the CopyIndex function. In other places, this is just extra code, which, moreover, makes it difficult to understand what is written.

With this knowledge, let us return to the properties and assume that we have the following class:
 class A { int value; public int Value { get { return @value; } set { @value = value; } } public A() { value = 5; } } 

You might think that the value field of class A will change in the Value property. But, in fact, this will not happen, and the result of the next program will be 5, not 10.
 static void Main(string[] args) { A a = new A(); a.Value = 10; Console.WriteLine(a.Value); } 

This behavior is due to a mismatch of @value in get and @value in set . @value in get will be nothing but a field of class A. And @value in set is actually a parameter to the set function. Thus, we simply write value to itself and in no way affect the value field in class A.

Collection initialization


To begin, recall the various ways to initialize arrays:
 string[] test1 = new string[] { "1", "2", "3" }; string[] test2 = new[] { "1", "2", "3" }; string[] test3 = { "1", "2", "3" }; string[,] test4 = { { "11", "12" }, { "21", "22" }, { "31", "32" } }; 

With lists, things are easier and there is only one option for initialization:
 List<string> test2 = new List<string>(){ "1", "2", "3" }; 

And finally, Dictionary :
 Dictionary<string, int> test = new Dictionary<string, int>() { { "aa", 1 }, { "bb", 2 }, { "cc", 3 } }; 

But for the sake of the following method I wrote this section, because I saw it for the first time:
 Dictionary<string, int> test = new Dictionary<string, int>() { ["aa"] = 1, ["bb"] = 2, ["cc"] = 3 }; 

A bit about LINQ queries


LINQ queries are, in principle, a convenient thing in themselves. We collect the chain with the necessary samples and at the output we obtain the necessary information. To begin, we will describe a couple of pleasant moments that may not come to mind until you see them yourself. First, consider the basic example:
 void Foo(List<int> numbers1, List<int> numbers2) { var selection1 = numbers1.Where(index => index > 10); var selection2 = numbers2.Where(index => index > 10); } 

It is easy to see that in the above example there are several identical checks. That is, in a good way, they can be put into a separate “function”:
 void Foo(List<int> numbers1, List<int> numbers2) { Func<int, bool> whereFunc = index => index > 10; var selection1 = numbers1.Where(index => whereFunc(index)); var selection2 = numbers2.Where(index => whereFunc(index)); } 

It has already become better, if the functions are large, then everything is fine. A little confused by the whereFunc call: some kind of it is unsightly. In fact, this is also not a problem:
 void Foo(List<int> numbers1, List<int> numbers2) { Func<int, bool> whereFunc = index => index > 10; var selection1 = numbers1.Where(whereFunc); var selection2 = numbers2.Where(whereFunc); } 

Now it is concise and tidy.

Now a little about the nuances of the operation of LINQ expressions. For example, a line of code will not lead to an instant sampling of data from the numbers1 collection.
 IEnumerable<int> selection = numbers1.Where(whereFunc); 

Data sampling will begin only when the sequence is converted to the List <int> collection:
 List<int> listNumbers = selection.ToList(); 

This nuance of work can easily lead to the use of a captured variable after its value has changed. Take a simple example. Suppose we need the function Foo, which returns from the array "{1, 2, 3, 4, 5}" only those elements whose numerical values ​​are less than the element index, that is:
 0 : 1 : 2 : 1 3 : 1, 2 4 : 1, 2, 3 

Let its signature be like this:
 static Dictionary<int, IEnumerable<int>> Foo(int[] numbers) { .... } 

And the call is this:
 foreach (KeyValuePair<int, IEnumerable<int>> subArray in Foo(new[] { 1, 2, 3, 4, 5 })) Console.WriteLine(string.Format("{0} : {1}", subArray.Key, string.Join(", ", subArray.Value))); 

Everything seems to be simple. Now we write the implementation itself based on LINQ. It will look like this:
 static Dictionary<int, IEnumerable<int>> Foo(int[] numbers) { var result = new Dictionary<int, IEnumerable<int>>(); for (int i = 0; i < numbers.Length; i++) result[i] = numbers.Where(index => index < i); return result; } 

As you can see, everything is extremely simple. We take and in turn "create" samples from the numbers array.

The result of the work of such a program is the following text in the console:
 0 : 1, 2, 3, 4 1 : 1, 2, 3, 4 2 : 1, 2, 3, 4 3 : 1, 2, 3, 4 4 : 1, 2, 3, 4 

The problem here is precisely in the closure, which occurred in lambda index => index <i . The variable i was captured, but since the call to the lambda expression index => index <i did not occur until the moment we asked for the result in the string.Join (",", subArray.Value) function , the value in it was not the same as at the time of forming the LINQ request. During data acquisition from the sample, the value of i was equal to 5, which led to an incorrect output.

Undocumented Crutches in C #


The C ++ language is known for its hacks, workarounds and other crutches, which is a series of XXX_cast functions. It is believed that in C # there is no such thing. In fact, this is not entirely true ...

Let's start with a few words:
These words are not in IntelliSense, and in MSDN there is no official description for them.

So what are these miracle words?

__makeref accepts an object and returns some “reference” to an object as an object of type TypedReference . And, actually, the words __reftype and __refvalue make it possible to find out from this “link” the type of the object and the value of the object from this “link”, respectively.

Consider an example:
 struct A { public int Index { get; set; } } static void Main(string[] args) { A a = new A(); a.Index = 10; TypedReference reference = __makeref(a); Type typeRef = __reftype(reference); Console.WriteLine(typeRef); //=> ConsoleApplication23.Program+A A valueRef = __refvalue(reference, A); Console.WriteLine(valueRef.Index); //=> 10 } 

But such a "fake ears" can be made a little more well-known means:
 static void Main(string[] args) { A a = new A(); a.Index = 10; dynamic dynam = a; Console.WriteLine(dynam.GetType()); A valuDynam = (A)dynam; Console.WriteLine(valuDynam.Index); } 

With dynamic lines and less, and less questions should cause people - "What is it?" And "How does it work?". But here's a slightly different scenario where working with dynamic does not look as good as with TypedReference .
 static void Main(string[] args) { TypedReference reference = __makeref(a); SetVal(reference); Console.WriteLine(__refvalue(reference, A).Index); } static void SetVal(TypedReference reference) { __refvalue(reference, A) = new A() { Index = 20 }; } 

The result of the work will be the output to the console of the number "20". Yes, you can also pass dynamic via ref to a function and it will work as well.
 static void Main(string[] args) { dynamic dynam = a; SetVal(ref dynam); Console.WriteLine(((A)dynam).Index); } static void SetVal(ref dynamic dynam) { dynam = new A() { Index = 20 }; } 

In my opinion, the version with TypedReference looks better, especially if the information is lower and lower, and lower in function.

In addition to the above, there is another miracle word __arglist, which allows you to make a function with a variable number of parameters, and even any type.
 static void Main(string[] args) { Foo(__arglist(1, 2.0, "3", new A[0])); } public static void Foo(__arglist) { ArgIterator iterator = new ArgIterator(__arglist); while (iterator.GetRemainingCount() > 0) { TypedReference typedReference = iterator.GetNextArg(); Console.WriteLine("{0} / {1}", TypedReference.ToObject(typedReference), TypedReference.GetTargetType(typedReference)); } } 

Strange is that it is impossible out of the box to organize the passage through the elements with the help of foreach , and you cannot directly contact an element from the list. So before C ++ or JavaScript, it does not hold out its arguments . :)
 function sum() { .... for(var i=0; i < arguments.length; i++) s += arguments[i] } 

In addition, I will provide a link to an article , from which I, in fact, learned what these words are like when I first encountered them.

Conclusion


In conclusion, I would like to say that C ++ and C # are languages ​​that are quite grammar-free, and thus, on the one hand, they are easy to use, but on the other hand, they do not protect against typos. There is a deep-rooted opinion that in C # one cannot be mistaken like in C ++, in fact this is not at all the case. This article presents quite interesting, in my opinion, the possibilities of the language, but the lion's share of errors in C # is not in them, but when writing ordinary if inductions, as, for example, in the Infragistics project.
 public bool IsValid { get { var valid = double.IsNaN(Latitude) || double.IsNaN(Latitude) || this.Weather.DateTime == Weather.DateTimeInitial; return valid; } } 

V3001 There are identical sub-expressions 'double.IsNaN (Latitude)' operator. WeatherStation.cs 25

Attention is scattered most often in precisely such moments, and then the long search for "it is not clear where it is not clear." So do not miss the opportunity to protect yourself from errors with the help of the PVS-Studio code analyzer.

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


All Articles