Novinky v C# 7.0


Out variables

V C# 7.0 byla vylepšena syntaxe pro out parametry. Dříve jste museli out proměnnou nejprve deklarovat a pak následně inicializovat. Nyní můžeme deklaraci přesunout do seznamu argumentů metody.

Z tohoto kódu:

int numericResult;
if (int.TryParse(input, out numericResult))
{
    WriteLine(numericResult);
}
else
{
    WriteLine("Could not parse input");
}

Se tak stane:

if (int.TryParse(input, out int result))
{
    WriteLine(result);
}
else
{
    WriteLine("Could not parse input");
}

Je zde také možnost použít var – v tomto případě pak jazyk sám určí jakého je proměnná typu podle návratového typu dané metody.

if (int.TryParse(input, out var answer))
{
    WriteLine(answer);
}
else
{
    WriteLine("Could not parse input");
}
  • Kód je lépe čitelný.
    • Deklarujeme proměnnou tam kde ji používáme. Nikoliv na jiném řádku.
  • Nemusíme přiřazovat počáteční hodnotu.

Tuples

Tuples nám umožňují vytvářet datové struktury s několika fieldy a následně je využívat pro návratové hodnoty metod.

private static (int Max, int Min) Range(IEnumerable<int> numbers)
{
    int min = int.MaxValue;
    int max = int.MinValue;
    foreach(var n in numbers)
    {
        min = (n < min) ? n : min;
        max = (n > max) ? n : max;
    }
    return (max, min);
}
 
var result = Range(numbers);

Console.WriteLine(result.Min);
Console.WriteLine(result.Max);
 
(int max, int min) = Range(numbers); // Deconstruction

Local functions

Za použití C# 7.0 máme možnost použít lokální funkci v těle metody a pak jí následně volat. Tato lokální je však dostupná pouze v dané metodě tudíž se k ní nedostaneme z jiných metod. Lokální funkce využijeme především nějakou metodu voláte POUZE z jedné metody – Celý návrh třídy se pak stává přehlednějším.

Z tohoto kódu:

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    return alphabetSubsetImplementation(start, end);
}

private static IEnumerable<char> alphabetSubsetImplementation(char start, char end)
{ 
    for (var c = start; c < end; c++)
        yield return c;
}

Se tak stane:

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

Pattern matching

Možnost pomocí klíčových slov is a switch řídit větvení kódu, popř. s možností dodatečných podmínek when. Je to podobné, jako C# 6.0 přišel s podmínkami u catch – bloků.

public static int SumTree(IEnumerable<object> values)
{
    var sum = 0;
    foreach(var item in values)
    {
        if (item is int val)
            sum += val; // leaf-node
        else if (item is IEnumerable<object> subList)
            sum += SumTree(subList);
    }
    return sum;
}
public static void SwitchPattern(object o)
{
    switch (o)
    {
        case null:
            Console.WriteLine("pattern pro konstantu");
            break;
        case int i:
            Console.WriteLine("Je to číslo!");
            break;
        case Person p when p.FirstName.StartsWith("Ka"):
            Console.WriteLine($"Jméno začíná na Ka: {p.FirstName}");
            break;
        case Person p:
            Console.WriteLine($"Jiné jméno: {p.FirstName}");
            break;
        case var x:
            Console.WriteLine($"jiný typ: {x?.GetType().Name} ");
            break;
        default:
            break;
    }
}

Ref locals and returns

Možnost použít referenci na proměnnou definovanou jinde.

public ref int Find(int number, int[] numbers)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == number) 
        {
            return ref numbers[i]; // vrať referenci, nikoliv hodnotu
        }
    }
    throw new IndexOutOfRangeException($"{nameof(number)} nenalezeno");
}
 
int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // vrátí referenci na pozici se sedmičkou
place = 9; // nahradí v poli nalezenou sedimičku devítkou
WriteLine(array[4]); // vypíše 9

More expression-bodied members

C# 6.0 přišel s Expression-bodied members pro metory a read-only property. V C# 7.0 se tato funkcionalita rozšiřuje na konstruktory, destruktory, property (get, set).

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
 
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
 
private string label;
 
// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

Throw expressions

Syntaxe je stejná jako dříve s tím rozdílem že můžeme throw můžeme umístit například do podmíněného výrazu.

public string Name
{
    get => name;
    set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}

Toto vylepšení umožní přesunout throw do inicializace. Dříve by pak tato část musela být v konstruktoru dané třídy.

Z tohoto kódu:

public ApplicationOptions()
{
    loadedConfig = LoadConfigResourceOrDefault();
    if (loadedConfig == null)
    {
        throw new InvalidOperationException("Could not load config");
    }
}

Se tak stane:

private ConfigResource loadedConfig = LoadConfigResourceOrDefault() ?? throw new InvalidOperationException("Could not load config");

Generalized async return types

Task<T> je referenční datový typ a jeho použití znamená alokaci objektu na haldě. Pro hodnotové návratové typy nově přichází optimalizace v podobě ValueTask<T>.

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

Numeric literal syntax improvements

C# 7.0 přináší dvě nové funkce, které usnadňují zápis číselných hodnot: binary literalsdigit separators.

Binary literals

Slouží pro vytváření bitových masek nebo binárního zápisu čísla. Hodnota „0b“ na začátku konstanty označuje, že číslo je zapsáno v binární podobě.

public const int One =  0b0001;
public const int Two =  0b0010;
public const int Four = 0b0100;
public const int Eight = 0b1000;

Digit separator

Slouží k oddělení čistitelných hodnot pomocí podtržítka „_“. Může být použit v jakékoliv konstantě.

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

public const long BillionsAndBillions = 100_000_000_000;

public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

Napsat komentář