⬆️ ⬇️

What's new in C # 7 and already supported in Visual Studio “15” Preview 4

With the release of Visual Studio “15” Preview 4, many new C # 7 features can be tried by yourself. The main innovations of C # 7 are designed to facilitate the work with data, simplify the code and improve performance. From myself I’ll say that C # is moving towards a functional language, adding things like tuples and pattern matching. Not all of the new functionality works as planned, in Preview 4, in these cases it will be indicated what you can still use and how it will work in the future. Well, let's get started.



Out variables



Now using out variables is not as easy as we would like. Before calling a method with out arguments, you must declare the variables that will be passed to this method. Since the values ​​are usually not assigned to these variables during the declaration (which is logical - they will still be overwritten by the method), the var keyword cannot be used. You need to declare variables with their type:



public void PrintCoordinates(Point p) { int x, y; //    p.GetCoordinates(out x, out y); WriteLine($"({x}, {y})"); } 


In C # 7, added out variables that allow you to declare variables immediately in a method call:



 public void PrintCoordinates(Point p) { p.GetCoordinates(out int x, out int y); WriteLine($"({x}, {y})"); } 


The scope for such variables is an external block, which is why you can use them in the expression following the method call.

')

Attention: In Preview 4, there are more stringent constraints on scope: out variables can only be used inside the expression where they were defined. Therefore, in order for the above example to work, you have to wait for the next release.



Since the out declaration of variables occurs in the same expression as passing them as method arguments, the compiler can infer their type (if there are no conflicting overloads for this method), so you can use the var keyword instead of type:



 p.GetCoordinates(out var x, out var y); 


Out arguments are widely used in the Try ... family of methods, where the returned boolean value indicates the success of the operation and out arguments contain the resulting value:



 public void PrintStars(string s) { if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); } else { WriteLine(" -  !"); } } 


Warning: In this example, i is used only inside the if block in which it is defined, so this example also works in Preview 4.



One possible improvement (which does not necessarily fall into C # 7) may be the use of wildcard characters (*) instead of those out parameters that will not be used further. For example:



 p.GetCoordinates(out int x, out *); //    x 


Pattern matching



In the seventh version of C #, the concept of a pattern (pattern) appears, which in general is a syntactic construct that allows you to check the correspondence of a variable to a specific pattern and extract information from it, if there is such a correspondence.



C # 7 has the following patterns:





This is just the beginning, and in the future we will definitely add new templates in C #. To support the templates 2 existing language constructs were changed:





In the future, we will add more options for using templates.



Templates with is



Consider a simple example that uses both a constant pattern and a type pattern.



 public void PrintStars(object o) { if (o is null) return; //   "null" if (!(o is int i)) return; //   "int i" WriteLine(new string('*', i)); } 


As you can see from the example, template variables (which were declared in the template) have the same scope as out variables, so they can be used inside the external visibility unit.



Note: In Preview 4, for template variables the same as for out variables, more stringent visibility rules apply, so the example will work only in future releases.



Templates and Try methods can be used together:



 if (o is int i || (o is string s && int.TryParse(s, out i)) { /*   i  int */ } 


Switch templates and expression



The use of switch options has been expanded, now you can:





Now consider an example:



 switch(shape) { case Circle c: WriteLine($"   {c.Radius}"); break; case Rectangle s when (s.Length == s.Height): WriteLine($"{s.Length} x {s.Height} "); break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} "); break; default: WriteLine("< >"); break; case null: throw new ArgumentNullException(nameof(shape)); } 


Note the following features of the new extended switch :





The scope for pattern variables declared in a case is the switch expression.



Tuples



Sometimes you want to return several values ​​from a method. None of the currently available methods looks optimal:





To simplify this task, tuples and tuple literals have been added to C # 7:



 (string, string, string) LookupName(long id) //   -  { ... //   return (first, middle, last); //   } 


The method now returns 3 strings combined into a tuple. The calling code can use them as follows:



 var names = LookupName(id); WriteLine($" {names.Item1} {names.Item3}."); 


The names of the fields Item1, Item2, ... are the default names for each tuple, however, it is possible to give the data combined into a tuple better names:



 (string first, string middle, string last) LookupName(long id) //       


Now the elements of the tuple can be accessed as follows:



 var names = LookupName(id); WriteLine($" {names.first} {names.last}."); 


You can also specify element names in the literal tuple immediately:



 return (first: first, middle: middle, last: last); //      


Tuples can be assigned to each other if the names of their elements do not match: the main thing is that the elements themselves can be assigned to each other. Restrictions will be added in the future, mainly for tuple literals that will signal errors such as randomly reversed element names, etc. In Preview 4, there are no such restrictions yet.



Tuples are a significant type, and their elements are mutable open fields. Tuples can be compared for equality: two tuples are equal (and have the same hash code) if all constituent elements are equal to each other in pairs (and have the same hash code). This behavior makes tuples useful not only for returning multiple values ​​from a method. For example, if you need a dictionary with a composite key, use a tuple as a key. If you need a list where there should be several values ​​at each position, it also uses a list of tuples. (From the translator: do not take this as a guide to using tuples in 100% situations, sometimes a simple class with a couple of properties will better express your intentions and will be easier to support in the future).



Note: Tuples in their work rely on types that are not already in Preview 4, but you can add them to your project using NuGet (do not forget to select “Include prerelease” and specify “nuget.org” as “Package source”), package called System.ValueTuple .



Unpacking Tuples



Another way to work with a tuple is to unpack it, which is to assign its elements to new variables:



 (string first, string middle, string last) = LookupName(id1); // deconstructing declaration WriteLine($" {first} {last}."); 


You can also use the var keyword instead of the type for each variable:



 (var first, var middle, var last) = LookupName(id1); // var  


Or even place a var in front of the brackets:



 var (first, middle, last) = LookupName(id1); // var  


You can also unpack a tuple into already declared variables:



 (first, middle, last) = LookupName(id2); //   


You can not only unpack a tuple, any type can be unpacked. To do this, it must have a method of the following form:



 public void Deconstruct(out T1 x1, ..., out Tn xn) { ... } 


Out parameters correspond to the values ​​that will be assigned as a result of unpacking. Why are out parameters and not tuples used? So that you can have several method overloads with a different number of parameters.



 class Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; } } (var myX, var myY) = GetPoint(); //  Deconstruct(out myX, out myY); 


This approach will allow you to create a "symmetric" constructor and unpacking method.

As for the out variables, we plan to add wildcard characters to ignore some return parameters.



 (var myX, *) = GetPoint(); //    myX 


Warning: It is still unknown whether wildcards will be added in C # 7.



Local functions



Sometimes an auxiliary function only makes sense within the single method in which it is called. Now this function can be declared inside the method:



 public int Fibonacci(int x) { if (x < 0) throw new ArgumentException("  !", nameof(x)); return Fib(x).current; (int current, int previous) Fib(int i) { if (i == 0) return (1, 0); var (p, pp) = Fib(i - 1); return (p + pp, p); } } 


The arguments of the external method and its local variables are available for the local function, as well as for lambda expressions.



As another example, consider the method implemented as an iterator. In this case, such a method usually requires a non-eager wrapper method to validate the arguments (because the iterator itself is not called until the MoveNext method is called).



With the help of local functions, this problem is solved more elegantly than usual:



 public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter) { if (source == null) throw new ArgumentNullException(nameof(source)); if (filter == null) throw new ArgumentNullException(nameof(filter)); return Iterator(); IEnumerable<T> Iterator() { foreach (var element in source) { if (filter(element)) { yield return element; } } } } 


If the Iterator method were an ordinary private method, then it could have been called accidentally, without checking the arguments. In addition, it would need to pass the same arguments to the Filter method.



Caution: In Preview 4, local functions must be declared before the call. In the future, this restriction will be relaxed: local functions can be called after all local variables used by them are assigned values.



Literal improvements



C # 7 now has the ability to add _ as a delimiter to numeric literals:



 var d = 123_456; var x = 0xAB_CD_EF; 


Separator can be added anywhere between the numbers, it does not affect the value.

Also in C # 7, binary literals appeared:



 var b = 0b1010_1011_1100_1101_1110_1111; 


Local variables and return values ​​by reference.



Now you can not only pass the parameters to the method by reference (using the ref keyword), but also return the data from the method by reference, and also save it to the local variable by reference.



 public ref int Find(int number, int[] numbers) { for (int i = 0; i < numbers.Length; i++) { if (numbers[i] == number) { return ref numbers[i]; //     ,      } } throw new IndexOutOfRangeException($"{nameof(number)}  "); } int[] array = { 1, 15, -39, 0, 7, 14, -12 }; ref int place = ref Find(7, array); //   ,   7   place = 9; //  7  9 WriteLine(array[4]); //  9 


Now it will be convenient to transfer references to specific places in large data structures. For example, in a game, information is contained in a large pre-allocated array of structures (to avoid pauses for garbage collection). Now methods can return a reference to one of such structures, with the help of which the calling code can read and modify this structure.



In order to work with links safely, the following restrictions were introduced:





Expansion of the list of types returned by asynchronous methods



Until today, async methods could return only void , Task or Task <T> . C # 7 now has the ability to create types that can also be returned by the asynchronous method. For example, you can create a ValueTask <T> structure that will help you avoid creating a Task <T> object when the result of an asynchronous operation is already available. For many asynchronous scenarios, for example, where buffering is used, this approach can significantly reduce the number of memory allocations and thus improve performance.



Of course, you can come up with other situations in which Task-like objects will be useful. Proper creation of these types will not be an easy task, so we do not expect a large number of developers to create them, but we think that in various frameworks they will be useful and the calling code can simply use await , as it is now for Task.



Warning: In Preview 4, these types are not yet available.



More class members in the form of expressions



Methods and properties in the form of expressions (expression bodied members), which appeared in C # 6, were often used, but not all types of members of a class could be so declared. Now support for setters, getters, constructors and destructors has been added to C # 7:



 class Person { private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>(); private int id = GetId(); public Person(string name) => names.TryAdd(id, name); //  ~Person() => names.TryRemove(id, out *); //  public string Name { get => names[id]; //  set => names[id] = value; //  } } 


This is an example of a new functionality added by the community, not by the compiler development team! Hurray, open source!



Warning: In Preview 4, support for these class members is not available.



Throw expressions



Throwing an exception in the middle of an expression is not so difficult: just call the method that does it. But in C # 7, you can now use throw as part of the expression:



 class Person { public string Name { get; } public Person(string name) => Name = name ?? throw new ArgumentNullException(name); public string GetFirstName() { var parts = Name.Split(" "); return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!"); } public string GetLastName() => throw new NotImplementedException(); } 


Warning: In Preview 4, such expressions are not yet available.



Conclusion



Although it’s still far from the release of C # 7, you can already play with most of the new features and understand where C # and .Net generally move (as for me, C # takes some of the features from functional languages, and this often makes the code more readable and less verbose. But everywhere you need to know when to stop, of course).

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



All Articles