Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 303 additions & 0 deletions docs/csharp/tutorials/exploration/csharp-7.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
### YamlMime:Tutorial
title: Explore C# 7.0 - C# interactive tutorial
metadata:
title: Explore C# 7.0 - Try the new features in C# 7.0 interactively, using your browser
description: In this tutorial, you'll use your browser to explore C# 7.0 interactively. You'll explore the new idioms you can use with C# 7.0 that enable more concise and readable code.
audience: Developer
level: intermediate
ms.date: 03/20/2019
displayType: two-column
interactive: csharp
items:
- durationInMinutes: 1
content: |
This tutorial lets you explore C# 7.0 features interactively, using your browser to write C# and see the results of compiling and running your code. It contains a series of lessons that modify earlier C# practices to use newer, more concise C# 7.0 features. The rest of this article provides an overview of each of these features, with a link to explore each feature.

- title: Out variable declarations at the assignment location
durationInMinutes: 2
content: |
The existing syntax that supports `out` parameters has been improved in this version. Click the *Enter Focus Mode* button at the bottom of this page, then try the following code in the interactive window:

[!code-csharp[OutVariableOldStyle](~/samples/snippets/csharp/new-in-7/program.cs#OutVariableOldStyle "classic out variable declaration")]

You can now declare `out` variables in the argument list of a method call, rather than writing a separate declaration statement. You can move the declaration into the method call. Add the following code to the bottom of the interactive window:

[!code-csharp[OutVariableDeclarations](~/samples/snippets/csharp/new-in-7/program.cs#OutVariableDeclarations "Out variable declarations")]

You can change the `int` declaration to a `var` declaration. Add the following code to the interactive window:

[!code-csharp[OutVarVariableDeclarations](~/samples/snippets/csharp/new-in-7/program.cs#OutVarVariableDeclarations "Implicitly typed Out variable")]

The new syntax provides two important advantages over the existing syntax:

* The code is easier to read.
- You declare the out variable where you use it, not on another line above.
* No need to assign an initial value.
- By declaring the `out` variable where it is used in a method call, you can't accidentally use it before it is assigned.

The declared variable's scope is the scope enclosing the `if` statement. This allows you to use the variable afterwards. Modify the last `if` block as shown in the following snippet.

```csharp
if (!int.TryParse(input, out int result))
{
return null;
}

Console.WriteLine(result);
```

- title: Tuples create light-weight data structures
durationInMinutes: 1
content: |
Tuples are lightweight data structures that contain multiple fields to represent the data members. The fields are not validated, and you cannot define your own methods.

> [!NOTE]
> Tuples were available before C# 7.0, but they were inefficient and had no language support. This meant that tuple elements could only be referenced as `Item1`, `Item2` and so on. C# 7.0 introduces language support for tuples, which enables semantic names for the fields of a tuple using new more efficient tuple types.

You can create a tuple by assigning a value to each named member:

[!code-csharp[NamedTuple](~/samples/snippets/csharp/new-in-7/program.cs#NamedTuple "Named tuple")]

The `namedLetters` tuple contains fields referred to as `Alpha` and `Beta`. Those names exist only at compile time and are not preserved at runtime (when inspecting the tuple using reflection, for example).

In a tuple assignment, you can also specify the names of the fields on the right-hand side of the assignment:

[!code-csharp[ImplicitNamedTuple](~/samples/snippets/csharp/new-in-7/program.cs#ImplicitNamedTuple "Implicitly named tuple")]

You can specify names for the fields on both the left and right-hand side of the assignment, but the names on the right side are ignored.

Tuples are most useful as return types for `private` and `internal` methods. Tuples provide a simple syntax for those methods to return multiple discrete values.

Creating a tuple is more efficient and more productive that creating a class or struct. It has a simpler, lightweight syntax to define a data structure that carries more than one value. The example method below returns the minimum and maximum values found in a sequence of integers:

[!code-csharp[TupleReturningMethod](~/samples/snippets/csharp/new-in-7/program.cs#TupleReturningMethod "Tuple returning method")]

There may be times when you want to unpackage the members of a tuple that were returned from a method. You can do that by declaring separate variables for each of the values in the tuple. This is called *deconstructing* the tuple. Add the following code in your browser to try it:

[!code-csharp[CallingWithDeconstructor](~/samples/snippets/csharp/new-in-7/program.cs#CallingWithDeconstructor "Deconstructing a tuple")]

As you work with tuples, you'll often find that you don't use all of the members of a tuple result. When that happens, you can discard one or more of the returned values by using `_` in place of a variable. Add the following code in your browser to try it:

[!code-csharp[DiscardTupleMember](~/samples/snippets/csharp/new-in-7/program.cs#DiscardMember "Discard a tuple member")]

You can learn more in depth about tuples in the [tuples article](../../tuples.md).
You can learn more about discards in the [discards article](../../discards.md).

- title: Use the type pattern with the is expression
durationInMinutes: 2
content: |
The `is` pattern expression extends the familiar [`is` operator](../../language-reference/keywords/is.md#pattern-matching-with-is) to query an object beyond its type.

Try the following code in your browser window:

[!code-csharp[SimpleIs](~/samples/snippets/csharp/new-in-7/patternmatch.cs#SimpleIsPattern "Simple Is pattern")]

Change the variable declaration to a string instead:

```csharp
object count = "5";
```

Now, the `is` expression is false, so the `else` branch is executed. Try to change `count` to `number` in the else branch:

```csharp
Console.WriteLine($"{number} is not an integer");
```

The above won't compile because `number` isn't assigned in the `else` branch. It's only assigned in the `true` branch of the `if` statement.

> [!NOTE]
> There is an [issue](https://github.com/dotnet/try/issues/175) where you may get incorrect output in the preceding example.

The `is` expression type pattern is useful when you have a small number of types to test against. Often, you may need to test multiple types. That requires the pattern matching `switch` statement.

- title: Pattern matching in the switch statement
durationInMinutes: 2
content: |
The *match expression* has a familiar syntax, based on the `switch`
statement already part of the C# language. Let's start with a small sample based on the `is` expression syntax you explored on the previous page:

[!code-csharp[SimpleSwitch](~/samples/snippets/csharp/new-in-7/patternmatch.cs#SimpleSwitchPattern "simple switch")]

The preceding code checks for an `int` or `null`. Every other type reached the default case. Add the following two lines to verify the behavior:

[!code-csharp[AddLongCase](~/samples/snippets/csharp/new-in-7/patternmatch.cs#TestLong "Add a case for long")]

The `switch` expression will convert a nullable type to its corresponding type. Add the following to verify:

[!code-csharp[NullableCase](~/samples/snippets/csharp/new-in-7/patternmatch.cs#NullableSwitch "Add a nullable case")]

You can add any number of other type pattern expressions to the switch statements. Add these before the `null` case:

[!code-csharp[MoreTypeCases](~/samples/snippets/csharp/new-in-7/patternmatch.cs#MoreCases "Add more type cases")]

Make sure these work by adding the following tests:

[!code-csharp[AddMoreTests](~/samples/snippets/csharp/new-in-7/patternmatch.cs#MoreTests "Add more type tests")]

The match expressions also support constants. This can save time by factoring out simple cases:

[!code-csharp[ConstantCase](~/samples/snippets/csharp/new-in-7/patternmatch.cs#ConstantCase "Add a constant case")]

You must add the preceding case *before* the `case int:` expression. If you add it after that case, the compiler warns you that it has already been handled by a previous case.

You can add a `when` clause to any pattern case so that you can test other conditions beyond a type or a constant value. Try it by adding the following case above the general `string` case:

[!code-csharp[WhenClause](~/samples/snippets/csharp/new-in-7/patternmatch.cs#WhenClause "Add a when clause")]

Test it with something like the following code:

[!code-csharp[TestWhenClause](~/samples/snippets/csharp/new-in-7/patternmatch.cs#TestWhenClause "Test the when clause")]

The new syntax for pattern matching expressions makes it easier to create dispatch algorithms using a clear and concise syntax based on an object's type or other properties. Pattern matching expressions enable these constructs on data types that are unrelated by inheritance.

You can learn more about pattern matching in the article dedicated to [pattern matching in C#](../../pattern-matching.md).

- title: Optimize memory storage using ref locals and returns
durationInMinutes: 2
content: |
This feature enables algorithms that use and return references to variables defined elsewhere. One example is with large matrices and finding a single location with certain characteristics. One method would return the two indices a single location in the matrix:

```csharp
public class Program
{
private static (int i, int j) Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return (i, j);
return (-1, -1); // Not found
}

public static void Main()
{
int[,] sourceMatrix = new int[10, 10];
for (int x = 0; x < 10; x++)
for (int y = 0; y < 10; y++)
sourceMatrix[x, y] = x * 10 + y;

var indices = Find(sourceMatrix, (val) => val == 42);
Console.WriteLine(indices);
sourceMatrix[indices.i, indices.j] = 24;
}

}
```


This `Find` method returns the indices to the item in the matrix. That leads callers to write code that uses those indices to dereference the matrix and modify a single element. You'd rather write a method that returns a *reference* to the element of the matrix that you want to change.

Let's walk through a series of changes to demonstrate the ref local feature and show how to create a method that returns a reference to internal storage. Along the way, you'll learn the rules of the ref return and ref local feature that protect you from accidentally misusing it.

Start by modifying the `Find` method declaration so that it returns a `ref int` instead of a tuple.

```csharp
private static ref int Find(int[,] matrix, Func<int, bool> predicate)
```

Modify the return statement to return the item at the correct indices:

```csharp
return matrix[i,j];
```

Change the final return to throw an exception instead:

```csharp
throw new InvalidOperationException("Not found");
```

Note that this won't compile. The method declaration indicates a `ref` return, but the return statement specifies a value return. You must add the `ref` keyword to each return statement. That indicates return by reference, and helps developers reading the code later remember that the method returns by reference:

```csharp
return ref matrix[i,j];
```

Now that the method returns a reference to the integer value in the matrix, you need to modify where it's called. The `var` declaration means that `valItem` is now an `int` rather than a tuple. Change the calling code in the `Main` method to the following:

```csharp
var valItem = Find(matrix, (val) => val == 42);
Console.WriteLine(valItem);
valItem = 24;
Console.WriteLine(matrix[4, 2]);
```

The second `WriteLine` statement in the example above prints out the value `42`, not `24`. The variable `valItem` is an `int`, not a `ref int`. The `var` keyword enables the compiler to specify the type but will not implicitly
add the `ref` modifier. Instead, the value referred to by the `ref return` is *copied* to the variable on the left-hand side of the assignment. The variable is not a `ref` local.

In order to modify the returned reference, you need to add the `ref` modifier to the local variable declaration and before the call to `Find` to make the variable a reference when the return value is a reference. Modify the `Main` method in your browser to match the following:

```csharp
public static void Main()
{
int[,] sourceMatrix = new int[10, 10];
for (int x = 0; x < 10; x++)
for (int y = 0; y < 10; y++)
sourceMatrix[x, y] = x * 10 + y;

ref var item = ref Find(sourceMatrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(sourceMatrix[4, 2]);
}
```

Now, the second `WriteLine` statement in the example above prints out the value `24`, indicating that the storage in the matrix has been modified. The local variable has been declared with the `ref` modifier, and it will take a `ref` return. You must initialize a `ref` variable when it is declared; you cannot split the declaration and the initialization.

The C# language has three other rules that protect you from misusing the `ref` locals and returns:

* You cannot assign a standard method return value to a `ref` local variable.
* You cannot return a `ref` to a variable whose lifetime does not extend beyond the execution of the method.
* `ref` locals and returns can't be used with async methods.

The addition of ref locals and ref returns enable algorithms that are more efficient by avoiding copying values or performing dereferencing operations multiple times.

For more information, see the [ref keyword](../../language-reference/keywords/ref.md) article.

- title: Minimize access to code with local functions
durationInMinutes: 2
content: |
You can now declare local functions that are nested inside other functions. This enables you to minimize the visibility of these functions. There are three obvious use cases for local functions:

- Recursive functions.
- Iterator methods.
- Asynchronous methods.

Let's start with recursive methods. Try the following code in the browser to calculate `6!` (factorial):

```csharp
int LocalFunctionFactorial(int n)
{
return nthFactorial(n);

int nthFactorial(int number) => (number < 2) ?
1 : number * nthFactorial(number - 1);
}

Console.WriteLine(LocalFunctionFactorial(6));
```

Local functions are a great way to implement recursive algorithms. Other common uses are for public iterator methods and public async methods. Both types of methods generate code that reports errors later than programmers might expect. In the case of iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In the case of async methods, any exceptions are only observed when the returned `Task` is awaited.

Iterator methods are easier to explore in the browser, so let's use those in this exploration. Try the following code that calls an iterator method in your browser:

[!code-csharp[IteratorMethod](~/samples/snippets/csharp/new-in-7/Iterator.cs#SnippetIteratorMethod "Iterator method")]

Run the code. Notice that the exception is thrown when the code begins iterating the second result set. The code that iterates the first result set has already run. This sample is both small and doesn't change any data structures, dso it's harmless and easy to fix. But, in a larger program, where the two iterator objects may be created in different child methods the root cause could be hard to find. If the first iterator method changed data state, it could even cause data corruption. You'd prefer the exception was thrown immediately, before any work is done. You can refactor the code so that the public method validates all arguments, and a local function that performs the enumeration:

[!code-csharp[IteratorMethodRefactored](~/samples/snippets/csharp/new-in-7/Iterator.cs#IteratorMethodLocalInteractive "Iterator method refactored")]

The preceding version makes it clear that the local method is referenced only in the context of the outer method. The rules for local functions also ensure that a developer can't accidentally call the local function from another location in the class and bypass the argument validation.

The same technique can be employed with `async` methods to ensure that exceptions arising from argument validation are thrown before the asynchronous
work begins.

> [!NOTE]
> Some of the designs that are supported by local functions
> could also be accomplished using *lambda expressions*. Those
> interested can [read more about the differences](../../local-functions-vs-lambdas.md).

- content: |
You've completed an exploration of the major new features in C# 7. Now try them yourself in your applications. You can see the full list in the [what's new in C# 7](../../whats-new/csharp-7.md) article.

2 changes: 1 addition & 1 deletion docs/csharp/whats-new/csharp-6.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ms.date: 12/12/2018

The 6.0 release of C# contained many features that improve productivity for developers. The overall effect of these features is that you write more concise code that is also more readable. The syntax contains less ceremony for many common practices. It's easier to see the design intent with less ceremony. Learn these features well, and you'll be more productive and write more readable code. You can concentrate more on your features than on the constructs of the language.

The rest of this article provides an overview of each of these features, with a link to explore each feature. You can also explore the features in an [interactive tutorial on C# 6](../tutorials/exploration/csharp-6.yml) in the tutorials section.
The rest of this article provides an overview of each of these features, with a link to explore each feature. You can also explore the features in an [interactive exploration on C# 6](../tutorials/exploration/csharp-6.yml) in the tutorials section.

## Read-only auto-properties

Expand Down
Loading