Tag Archives: C# Language

C# 7.0 – Rychlý přehled novinek

Out variables

Možnost deklarovat out-proměnné inline při volání metod:

// dříve
int x;
if (int.TryParse(s, out x)) ...

// C# 7.0
if (int.TryParse(s, out int x)) ...

// C# 7.0
if (int.TryParse(s, out var x)) ...

…rozhodně přehlednější zápis.

Tuples

Možnost vytvářet lehké datové struktury s několika fieldy a používat je i jako 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.Write($"{result.Min} - {result.Max}");

(int max, int min) = Range(numbers); // deconstruction

Bude zneužíváno lenochy, co nechtějí psát malé ViewModely, ale budou raději riskovat zmatky při deconstruction (záleží na pořadí prvků, ne názvech!).

Lokální funkce

Možnost definovat pomocné lokální funkce uvnitř metod:

public IEnumerable<MyClass> GetActiveItems()
{
    var rawData = myClassDataService.GetPrimaryItems().Where(i => IsActive(i));
    if (!rawData.Any())
    {
        return myClassDataService.GetSecondaryItems().Where(i => IsActive(i));
    }
    return rawData;

    bool IsActive(MyClass item)
    {
        return (item.State == States.Active) && (item.Rows.Any());
    }
}

Dosud bylo možné použít anonymní lambda výrazy, syntaxe je však nyní trochu přívětivější. Zde například chci 2x vyhodnotit stejnou podmínku IsActive, ale z jiné metody ve třídě to nepotřebuji a lokální funkce je tak pro mě přehlednější, než samostatná privátní metoda.

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:

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;
    }
}

Je to podobné, jako C# 6 přišel s podmínkami u catch-bloků. Při if-is testech na typ se bude hodit.

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

Využití je celkem specifické, ale výjimečně se může hodit.

Další expression-bodied members

// 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";
}

…snad jedině u jednoduchých properties to zlepšuje čitelnost, jinak nejsem moc velkým příznivcem expression-bodies members.

Vyhazování výjimek z výrazů

Hodí se třeba v kombinaci s exression-bodied members:

public string Name
{
    get => name;
    set => name = value ?? throw new ArgumentNullException();
}

ValueTask pro async

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;
}

Čitelnější možnosti zápisu číselných konstant

public const int One =  0b0001; // binární číslo
public const int Sixteen =   0b0001_0000; // oddělovač (lze použít kdekoliv, ignoruje se)
public const long HodneMoc = 100_000_000_000;
public const double AvogadroConstant = 6.022_140_857_747_474e23;

…binární zápis se může hodit na flagy, oddělovač na velká čísla.

C# 6.0: Ostatní (extension-Add v initializers, overload resolution) [10/10]

Do C# 6.0 se dostaly ještě dvě „drobotiny“, které většině vývojářů život nijak zásadně nezmění.

Collection-initializers podporují extension-podobu Add() metody

V C# 5.0, v inicializaci kolekcí, nemohla být použita extension-podoba metody Add(), přestože VB.NET toto měl. Byl to spíše bug/opomenutí. Každopádně nyní je to napraveno.

Vylepšené vyhodnocování overloadů

Drobná vylepšení, kterých si málokdo všimne. O něco málo méně míst, kde se můžete setkat s výtkou kompilátoru ohledně nejednoznačnosti volby overloadu (např. u nullable hodnotových typů parametrů, atp.)

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: Konstruktor struct bez parametrů [9/10] – vyřazeno z RTM

Tato úprava jazyka není součástí C# 6.0 RTM, byla odstaněna.

C# 5.0 nedovoluje vytvořit na struct explicitní constructor bez parametrů, hodnoty fieldů jsou totiž automaticky inicializovány svými default(T) hodnotami. C# 6.0 toto omezení odstraňuje:

struct Person
{
    public string Name { get; }
    public int Age { get; }
   
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public Person() : this("Jane Doe", 37)
    { }
}

Důležitým omezením však je, že takový konstruktor se použije pouze v případě explicitního volání new Person(), zatímco default(Person) stále založí výchozí hodnoty fieldů, stejnětak vytvoření pole new Person[..].

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: Exception filters, await v catch/finally bloku [8/10]

C# 6.0 dohání chybějící drobnosti kolem výjimek

Exception filters

Catch-blok je vyvolán, pouze pokud je vedle typu výjimky splněna i podmínka:

try
{
    … 
}
catch (MyException ex) if (condition) // už v podmínce se lze odkázat na ex
{
    …
}

Znásilnit tuto konstrukci lze i pro neinvazivní logování výjimek, sám bych s tím však byl opatrný.

private static bool Log(Exception e) { /* log it */ ; return false; }
…
try { … } catch (Exception e) if (Log(e)) {}

Await v catch a finally blocích

C# 5.0 nepodporoval await v catch a finally blocích, protože se Microsoft domníval, že to není potřebné a implementace by byla neúměrně komplikovaná. Nyní dlužnou funkčnost doplnili:

Resource res = null;
try
{
    res = await Resource.OpenAsync(…);
}
catch(ResourceException e)
{
    await Resource.LogAsync(res, e);
}
finally
{
    if (res != null) await res.CloseAsync();
}

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: Index initializers – přehlednější inicializace slovníků [7/10]

Jednou z předchozích novinek v C# byla možnost při založení kolekce/slovníku naplnit ho rovnou i počátečními hodnotami. C# 6.0 přichází se zpřehledněním zápisu pro inicializaci slovníků (a podobných objektů s indexery):

// C# 5.0
var numbers = new Dictionary<int, string> {
    {7, "seven"},
    {9, "nine"},
    {13, "thirteen" }
};

// C# 6.0
var numbers = new Dictionary<int, string> {
    [7] = "seven",
    [9] = "nine",
    [13] = "thirteen"
};

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: nameof() expressions [6/10]

Přestože se nová FluentAPI snaží vyhnout textovým řetězcům ve prospěch „strong-type“ zápisů, stále se na mnoha a mnoha místech setkáváme s potřebou zapsat do kódu textový řetězec, který odkazuje na název nějakého prvku. Potíže s tím související jsou nasnadě – kompilátor nám takový textový řetězec nekontroluje a jakýkoliv automatický refactoring v podobě přejmenování prvku zanechá textový řetězec nedotčen. Jde tak o častý zdroj chyb.

C# 6.0 přináší pomůcku, kterou se dá těmto problémům vyhnout. Konstrukci nameof(prvek) převede kompilátor na textovou konstantu odpovídající názvu prvku:

// C# 5.0
(if x == null) throw new ArgumentNullException("x");

// C# 6.0
(if x == null) throw new ArgumentNullException(nameof(x));

Uvnitř nameof() může být prakticky cokoliv, co má jméno – proměnná, typ, metoda, property, atp. Vždy se však použije pouze poslední část jména:

Console.WriteLine(nameof(person.Address.ZipCode)); // vrátí "ZipCode"
Console.WriteLine(nameof(System.Console.WriteLine)); // vrátí "WriteLine"

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: String interpolation – zkrácený String.Format() [5/10]

Velmi často používaná metoda String.Format() získala v C# 6.0 syntaktic-sugar v podobě zkráceného zápisu, který má být přehlednější a méně náchylný na chyby:

// C# 5.0
var s = String.Format("{0} is {1} year{{s}} old", p.Name, p.Age);

// C# 6.0 cílová podoba s $ na začátku
var s = $"{p.Name,20} is {p.Age:D3} year{{s}} old";

// C# 6.0 ve VS2015 Preview
var s = "\{p.Name} is \{p.Age} year{s} old";

Půjde samozřejmě použít i další formátovací instrukce, např.

var s = $"{p.Name,20} is {p.Age:D3} year{s} old";
var s = $"{p.Name} is {p.Age} year{(p.Age == 1 ? "" : "s")} old";

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)