C# 7 is available on new Visual Studio 2017 and comes with some new features. I wouldn’t call them ‘revolutionary’ features, but they add on the language very well and some of them can be very helpful. Personally, I have anticipated some features since C# 6 version.
TLDR; Sample code can be found here.
C# 7 comes with new features that I really like and I strongly believe they will be very useful in daily coding life, while others I think won’t help me anytime soon.
Look at C# Language Design on GitHub or the proposals made from the community for the language, to get an idea of the new language features and the features to come.
Before I begin, I wish to share my experience with VS 2017, C# 7, .NET Standard/Core.
I used Visual Studio 2017 Community version to experiment with new syntax (I don’t have yet a license for Pro, sniff). I also wanted to experiment on code by writing some unit tests (this is my way to learn new stuff, I first write a unit test and then fiddle around with code, aiming to make it pass), so I used xUnit (which I am not a big fan of, but anyways). Both projects were .NET Standard 1.4 projects, but then I realized that this doesn’t work, as you can see on my SOpost. So, the answer was to make the test project a runnable application, I used .NET Core 1.1 but with no luck, so what I did in the end, was to create a new test project, targeting .NET Framework 4.6.2. Project with samples remained to .NET Standard.
Also, do not forget to use the System.Runtime Nuget package. Take a look on samples at Github (link is available above).
Out variables
Man, I love this. It is the first topic to discuss because I anticipated this for a long time. Personally, I really, really hated the old way, where you needed to declare a variable before calling the method with the output parameter. I was feeling funny everytime I was forced to code stuff like this:
int value;
int .TryParse( "5" , out value);
// Use value |
I strongly believe that this is not elegant and I was really disappointed when they decided to not include this on C# 6 release.
But, here it is. Now you can declare the variable and the out parameter on the same line
int .TryParse( "5" , out int value);
// Use value |
Yeah, I know, this isn’t exactly a revolution or an outstanding feature, but it is helpful and increases readability (of course, you need to get your eyes used to this new syntax, as you might lose a variable declaration while reading). Please note also that you can declare it as a var,
instead of a specific type.
As it is expected, you can declare more than one output parameters (duh!) and use this new syntax. The cool thing with this though, is that you can decide which output parameter you wish and which you don’t. Cool stuff!
Bear with me, I know, the following example is a bit dull, but it demonstrates the use of discards (which can be declared with an underscore)
// Usage with two output parameters, fetching the first CustomConverter.GetIntegerFromStringWithDiscards( out var x, out _);
// Usage with more than two output parameters, fetching the first CustomConverter.GetIntegerFromStringWithDiscards( out var x, out _, out _);
// Usage with three output parameters, fetching the second CustomConverter.GetIntegerFromStringWithDiscards( out _, out var y, out _);
|
As you can see, you omit the parameter you don’t wish to return using out _. How cool is the third example? You discard the first and the last and you get the middle one. Nice!
There is one gotcha though and this has to do with scoping. The scope rule is strict, having the variables accessible only in the scope body they are declared in.
That said, the following code will not work, you will get a compiler error, stating that the name ‘[name]’ does not belong to current context.
public static void GetIntegerFromStringWithDiscards( out int x, out int y)
{ if ( true ) {
int .TryParse( "5" , out var a);
int .TryParse( "6" , out var b);
}
x = a;
y = b;
} |
but the following will work just fine
public static int GetIntTimesTenOrDefault( string number)
{ if ( int .TryParse(number, out int value)) {
return value * 10;
}
// Value variable is visible to this scope, not only inside the if scope
return value;
} |
Tuples and deconstruction
The new syntax of tuples is awesome! I really like it and it kinda reminds me of JavaScript objects in some weird way, probably it’s just me, of course the syntax is not the same, but it reminds me that whenever I want to fetch a tuple value with that syntax.
Please note, in order to use the new tuples, you need to download a Nuget package, as it is not yet on core language code. So, that said, download the
System.ValueTuple 4.3.0
(or higher)
Forget the old, rusty, rigid syntax for tuples, now you just declare them by type and you have them.
Look on the following example, the GetPerson
method returns a Tuple with two strings and an integer.
public ( string name, string surname, int age) GetPerson()
{ return ( "George" , "Dyrrachitis" , 28);
} |
The syntax is pretty much simple. For declaration, you wrap in parenthesis the tuple items with their types and optionally names for them, if you want to make the signature more readable.
In order to create a tuple instance, you wrap your objects in parenthesis, of course matching the declaration, like in the example above, first define the two strings, then the integer value.
You consume it in a similar fashion. Of course, you can consume a simple tuple object and access each item individually like this:
var result = person.GetPerson();
// result.Item1 // result.Item2 // result.Item3 |
But this is not sexy, okay? What about this?
// Act ( string firstName, string lastName, int age) = person.GetPerson();
// Assert Assert.Equal( "George" , firstName);
Assert.Equal( "Dyrrachitis" , lastName);
Assert.Equal(28, age); |
That’s what I’m talking about! You can wrap your tuple elements in parenthesis, declare them and they are ready to be used like normal variables. Of course, you can declare them as var
as well or even better, for shorthand syntax (see deconstruction below):
var (firstName, lastName, age) = person.GetPerson();
|
Declaring them all as vars.
Also, like out variables, tuples have discards as well, so you can omit elements you don’t wish to use, just like at the following example.
[Fact] public void GetOnlyFirstNameGeorgeFromTuple()
{ // Arrange
var person = new Person();
// Act
( var firstName, _, _) = person.GetPerson();
// Assert
Assert.Equal( "George" , firstName);
} [Fact] public void GetFirstNameAndAgeFromTuple()
{ // Arrange
var person = new Person();
// Act
( var firstName, _, var age) = person.GetPerson();
// Assert
Assert.Equal( "George" , firstName);
Assert.Equal(28, age);
} |
Finally, on tuples, there is another interesting feature, called deconstruction. We looked at that feature on the previous examples, with the sexy syntax.
But you can use it on classes as well, making them to behave like a tuple. You just need to declare a method called Deconstruct
in your class, which can receive a number of output parameters, which will be deconstructed.
public class Person
{ public Person( string firstName, string lastName, int age)
{
FirstName = firstName;
LastName = lastName;
Age = age;
}
public string FirstName { get ; }
public string LastName { get ; }
public int Age { get ; }
public void Deconstruct( out string firstName, out string lastName, out int age)
{
firstName = FirstName;
lastName = LastName;
age = Age;
}
} |
In the code above, I want to deconstruct a Person
object to strings firstName,
lastName
and integer age
. So, I use the Deconstruct
method, declaring three output parameters, matching the description I gave earlier. I just give them the values from the public properties of the class, which were populated on instantiation.
Now, Person
can be deconstructed like this:
[Fact] public void DeconstructPersonObjectWithDeconstructor()
{ // Arrange
var person = new Person( "George" , "Dyrrachitis" , 28);
// Act
var (firstName, lastName, age) = person;
// Assert
Assert.Equal( "George" , firstName);
Assert.Equal( "Dyrrachitis" , lastName);
Assert.Equal(28, age);
} |
Groovy!
Pattern matching
Although pattern matching is a hot feature for the language, I won’t spend much time discussing that.
I would say that pattern matching is not that really useful to the language, it was a feature ‘nice to have’ mostly, by reading the patterns.md proposal, at least that is my take.
You can think of it as the Is
operator in steroids. That operator is extended to test an expression against a pattern, and in this case we have various types of patterns.
We can match an expression based on type or constant value or null and other discussed here.
This feature is very useful in some coding scenarios. Mostly, I like it because it gives me better syntax in an is-a situation or in testing a nullable type. Seriously, I like it only for that.
Check on the following example (of course the following can shorten significantly by using a ternary operator, but for the sake of the example I am more explicit)
public class TypesWithIsOperator
{ public bool IsCustomReferenceType( object obj)
{
if (obj is CustomReferenceType t)
{
return t.True;
}
return false ;
// or for short: return obj is CustomReferenceType t ? t.True : false;
}
public bool IsNullableIntGreaterThanTen( int ? value)
{
if (value is int v)
{
return v > 10;
}
return false ;
// or for short: return value is int v ? v > 10 : false;
}
} public class CustomReferenceType
{ public bool True => true ;
} |
In Is
CustomReferenceType
method, I check if the object passed is of type CustomReferenceType
and I create a new variable t
of that type from the object (same as using the as
operator and assigning the result to a new variable). Same goes for the IsNullableIntGreaterThanTen
method, in which I check if the nullable integer value passed is not null, create a new variable v
, assing the integer value there and return Boolean result based on its value.
In essence, I avoid doing this:
// With reference type var t = obj as CustomReferenceType;
if (t != null ) {
return t.True;
} return false ;
// Or with value type if (value.HasValue) {
int v = value.Value;
return v > 10;
} return false ;
|
Another coding scenario that pattern matching is useful and is demonstrated by almost everyone, is when using a switch-case statement. Now, you can match a case block by a pattern, which can be a type or a constant expression or null.
Be careful though, the patterns are not matched in an ordered fashion, as the compiler can match patterns out of order, as it is optimized to reuse the results of an already matched pattern in order to compute the result of matching of other patterns.
Also, note that the default
case will execute last, regardless of the order you specify it on code.
public string MatchingMachineProduct(IMachine machine)
{ switch (machine)
{
case PizzaMachine pizzaMachine:
return pizzaMachine.Make();
case FishAndChipsMachine fishAndChipsMachine:
return fishAndChipsMachine.Make();
default :
return null ;
case null :
throw new ArgumentNullException(nameof(machine));
}
} |
Other features
Local functions
Other features that I like are the local functions. I think this is a good addition and improves readability.
When reading on code, as a human being, I read from top to bottom and jumping to references while reading, meaning I will jump to the next function declared. I prefer to write a private function immediately after its call, so a reader can jump to it directly, without much effort searching in the class.
With local functions, you literally have the chance to write it just after your call in the caller’s body. All the caller’s scoped variables are available within the local function, which helps you to define more readable signatures, with less parameters (the more the parameters a method has, the dirtier it is).
Of course, this is one advantage that it gives you, the other is scoping, the only one that has access to this method is the caller and only that. This can help you create more meaningful inner functions, closer to the caller’s context.
public class Bubblesort
{ private readonly int [] _array;
public Bubblesort( int [] array) => _array = array;
public void Sort()
{
for ( var i = _array.Length - 1; i > 1; i--)
for ( var j = 0; j < i; j++) if (_array[j] > _array[j + 1])
Swap(j, j + 1);
void Swap( int i, int j)
{
var temp = _array[i];
_array[i] = _array[j];
_array[j] = temp;
}
}
} |
Be careful though, not to overdo it, as this can lead to some insanely huge looking parent methods, which is the worst thing you can do with your code. As a rule of thumb, use local functions only when you know they will be short, else use other private methods and try to separate concerns, do not make methods more 10-20 lines long.
Always remember:
- 1-10 lines is perfect
- 10-20, you need a short check on code
- 20-30, it’s getting out of hand
- 50-60+, Houston, we have a problem
If your method does not fit your screen, then you are in some deep trouble. And don’t cheat with pivot screens.
Expression bodied members
Finally, this is another great feature. I loved expression bodied members in the previous release, my single line methods looked so good with that syntax.
With the new syntax, constructors, destructors, even getters and setters can have expression bodies.
Look on the previous example, the constructor has an expression body.
Throw expressions
You can throw an exception when using the ternary conditional operator (?:
), the null coalescing operator (??
) or as the body of an expression-bodied lamda or method.
Take a look at the following examples:
// Using the ternary conditional operator return condition ? "Yes, it's true" : throw new Exception( "Oops" );
// Using the null coalescing operator return value ?? throw new Exception( "Oops" );
// Expression-bodied method public void Throws() => new Exception( "Oops" );
|
Summary
Cool new features, improving your experience with the language.
I haven’t been through many other features, maybe the most notorious of them, the pattern matching. This is on purpose. As I said in the beginning, my intention was to go through stuff that I find useful in my daily routine, and for the moment, I do not find pattern matching that useful for me. Probably I need to dig deeper and see the advantages, but for the moment I do not use it.
What new features do you use? Do they come in handy for you?
请发表评论