Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

References

Original Tuples language feature proposal on GitHub

A runnable VS 15 solution for C# 7.0 features

NuGet Tuple Package

Section 71.2: Local functions

Local functions are defined within a method and aren't available outside of it. They have access to all local variables and support iterators, async/await and lambda syntax. This way, repetitions specific to a function can be functionalized without crowding the class. As a side e ect, this improves intellisense suggestion performance.

Example

double GetCylinderVolume(double radius, double height)

{

return getVolume();

double getVolume()

{

//You can declare inner-local functions in a local function double GetCircleArea(double r) => Math.PI * r * r;

//ALL parents' variables are accessible even though parent doesn't have any input. return GetCircleArea(radius) * height;

}

}

Local functions considerably simplify code for LINQ operators, where you usually have to separate argument checks from actual logic to make argument checks instant, not delayed until after iteration started.

Example

public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate)

{

if (source == null) throw new ArgumentNullException(nameof(source));

if (predicate == null) throw new ArgumentNullException(nameof(predicate));

return iterator();

IEnumerable<TSource> iterator()

{

foreach (TSource element in source) if (predicate(element))

yield return element;

}

}

Local functions also support the async and await keywords.

Example

async Task WriteEmailsAsync()

{

var emailRegex = new Regex(@"(?i)[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z0-9-.]+"); IEnumerable<string> emails1 = await getEmailsFromFileAsync("input1.txt"); IEnumerable<string> emails2 = await getEmailsFromFileAsync("input2.txt"); await writeLinesToFileAsync(emails1.Concat(emails2), "output.txt");

GoalKicker.com – C# Notes for Professionals

403

async Task<IEnumerable<string>> getEmailsFromFileAsync(string fileName)

{

string text;

using (StreamReader reader = File.OpenText(fileName))

{

text = await reader.ReadToEndAsync();

}

return from Match emailMatch in emailRegex.Matches(text) select emailMatch.Value;

}

async Task writeLinesToFileAsync(IEnumerable<string> lines, string fileName)

{

using (StreamWriter writer = File.CreateText(fileName))

{

foreach (string line in lines)

{

await writer.WriteLineAsync(line);

}

}

}

}

One important thing that you may have noticed is that local functions can be defined under the return statement, they do not need to be defined above it. Additionally, local functions typically follow the "lowerCamelCase" naming convention as to more easily di erentiate themselves from class scope functions.

Section 71.3: out var declaration

A common pattern in C# is using bool TryParse(object input, out object value) to safely parse objects.

The out var declaration is a simple feature to improve readability. It allows a variable to be declared at the same time that is it passed as an out parameter.

A variable declared this way is scoped to the remainder of the body at the point in which it is declared.

Example

Using TryParse prior to C# 7.0, you must declare a variable to receive the value before calling the function:

Version < 7.0

int value;

if (int.TryParse(input, out value))

{

Foo(value); // ok

}

else

{

Foo(value); // value is zero

}

Foo(value); // ok

In C# 7.0, you can inline the declaration of the variable passed to the out parameter, eliminating the need for a separate variable declaration:

Version ≥ 7.0

if (int.TryParse(input, out var value))

GoalKicker.com – C# Notes for Professionals

404

{

Foo(value); // ok

}

else

{

Foo(value); // value is zero

}

Foo(value); // still ok, the value in scope within the remainder of the body

If some of the parameters that a function returns in out is not needed you can use the discard operator _.

p.GetCoordinates(out var x, out _); // I only care about x

An out var declaration can be used with any existing function which already has out parameters. The function declaration syntax remains the same, and no additional requirements are needed to make the function compatible with an out var declaration. This feature is simply syntactic sugar.

Another feature of out var declaration is that it can be used with anonymous types.

Version ≥ 7.0

var a = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var groupedByMod2 = a.Select(x => new

{

Source = x, Mod2 = x % 2

})

.GroupBy(x => x.Mod2)

.ToDictionary(g => g.Key, g => g.ToArray()); if (groupedByMod2.TryGetValue(1, out var oddElements))

{

Console.WriteLine(oddElements.Length);

}

In this code we create a Dictionary with int key and array of anonymous type value. In the previous version of C# it was impossible to use TryGetValue method here since it required you to declare the out variable (which is of anonymous type!). However, with out var we do not need to explicitly specify the type of the out variable.

Limitations

Note that out var declarations are of limited use in LINQ queries as expressions are interpreted as expression lambda bodies, so the scope of the introduced variables is limited to these lambdas. For example, the following code will not work:

var nums =

from item in seq

let success = int.TryParse(item, out var tmp)

select success ? tmp : 0; // Error: The name 'tmp' does not exist in the current context

References

Original out var declaration proposal on GitHub

Section 71.4: Pattern Matching

Pattern matching extensions for C# enable many of the benefits of pattern matching from functional languages, but in a way that smoothly integrates with the feel of the underlying language

GoalKicker.com – C# Notes for Professionals

405

switch expression

Pattern matching extends the switch statement to switch on types:

class Geometry {}

class Triangle : Geometry

{

public int Width { get; set; } public int Height { get; set; } public int Base { get; set; }

}

class Rectangle : Geometry

{

public int Width { get; set; } public int Height { get; set; }

}

class Square : Geometry

{

public int Width { get; set; }

}

public static void PatternMatching()

{

Geometry g = new Square { Width = 5 };

switch (g)

{

case Triangle t:

Console.WriteLine($"{t.Width} {t.Height} {t.Base}"); break;

case Rectangle sq when sq.Width == sq.Height: Console.WriteLine($"Square rectangle: {sq.Width} {sq.Height}"); break;

case Rectangle r: Console.WriteLine($"{r.Width} {r.Height}"); break;

case Square s: Console.WriteLine($"{s.Width}"); break;

default: Console.WriteLine("<other>"); break;

}

}

is expression

Pattern matching extends the is operator to check for a type and declare a new variable at the same time.

Example

Version < 7.0

string s = o as string; if(s != null)

{

// do something with s

}

GoalKicker.com – C# Notes for Professionals

406