Blog (220)
Komentarze (4k)
Recenzje (1)
@djfoxerNowości w C# 6 — cóż ciekawego otrzymujemy?

Nowości w C# 6 — cóż ciekawego otrzymujemy?

18.07.2016 | aktual.: 21.07.2016 10:44

Tak, tak, tak. C# 6 jest już z nami od jakiegoś już czasu, ale w życiu nie jest tak kolorowo i nie wszyscy mogli przejść na nowego Visual Studio 2015 tuż po tym jak się ukazał. Dodatkowo nawet jeśli ktoś już przesiadł się na najświeższe IDE od MS, to i tak nie zawsze mógł używać nowości, które wpadły wraz z C# 6.

Zatem dla niektórych będzie to przypomnienie, dla innych zapoznanie się z nowościami. Co więcej, w sieci jest wiele stron opisujących nowe elementy w C#, które... nie znalazły się w finalnym wydaniu.

Sam C# 6 nie przynosi olbrzymich zmian czy nowości. W tym wydaniu nastawiono się głównie na wprowadzenie małych ficzerów, które uprzyjemnią pracę z kodem i zmniejszą jego ilość, zwiększając przy tym czytelność.

Cóż ciekawego pojawi się zatem w wraz z C# 6?

597401

Operator ?.

To chyba jedna z bardziej wyczekiwanych nowości w C# 6. Zmorą deweloperów tworzących w C# jest wyjątek NullReferenceException. Powoduje to często, że kod w wielu miejscach złożony jest if‑ów, w których sprawdzamy czy coś nie jest nullem.

Klasycznie:


if (client != null &&
    client.Baskets != null &&
    client.Baskets.Any(x => x.Items != null && x.Items.Length > 1))
{
    Console.WriteLine("OK!");
}

Od teraz możemy zastąpić sprawdzanie czy zmienna nie jest nullem poprzez użycie operatora ?. zwanego potocznie Elvis operatorem (przekręćcie głowę, aby zobaczyć Króla RnR)

C# 6:


if (client?.Baskets?.Any(x => x.Items?.Length > 0) ?? false)
{
    Console.WriteLine("OK!");
}

Warto odnotować, że używając operatora możemy uprzyjemnić sobie życie z delegatami.

Do tej pory jeśli chcieliśmy odpalić delagat, dbając o wątki, należało zrobić to w następujący sposób:


var onChanged = OnChanged;
if (onChanged != null)
{
    onChanged(this, args);
}

Za pomocą ?. ten sam kod (thread-safe) w C# 6 napiszemy:


OnChanged?.Invoke(this, args);

Właściwości - inicjalizacja

Możemy już inicjalizować właściwości, podobnie jak w przypadku pól:


public class Customer
{
    public string First { get; set; } = "Jon";
    public string Last { get; set; } = "Snow";
}

Warto dodać, że inicjalizacja nie przebiega poprzez set, ale wartość jest nadawana bezpośrednio.

Właściwości - readonly


public class Customer
{
    public string First { get; } = "Jon";
    public string Last { get; }

    public Customer(string last)
    {
        Last = last;
    }
}

Właściwości można od teraz tworzyć bez użycia settera. W takim wypadku niejawnie tworzone jest pole readonly. Właściwość może być znacjonalizowana tylko bezpośrednio (jak wyżej) lub w konstruktorze.

String interpolation

Kolejną ciekawią nowością jest interpolacja Stringów. Zamiast używać String.Format możemy to samo zrobić w znacznie krótszy sposób:


string first = "Jon", last = "Snow";

var s_old = String.Format("{0} likes {1}", first, last);

var s_csharp6 = $"{first} likes {last} now {DateTime.Now:d}";

Metody, właściwości i indeksatory jako pojedyncze wyrażenie lambda

W C# 6 dostaliśmy możliwość zapisywania metod, będących pojedynczymi wyrażeniami, w prostej i zwięzłej formule. Oczywiście metody takie mogą również zwracać void.

Klasycznie:


public decimal AddMoney(decimal toAdd)
{
    return Money += toAdd;
}

C# 6:


public decimal AddMoney(decimal toAdd) => Money += toAdd;

Funkcjonalność ta została rozszerzona także o właściwości i indeksatory. W przypadku tych pierwszych tworzymy wyliczanlne właściwości (tylko do odczytu).

Klasycznie:


public string FullName
{
    get
    {
        return First + " " + Last;
    }
}

C# 6:


public string FullName => First + " " + Last;

W ten sposób otrzymujemy znacznie lżejszy kod od strony wizualnej. Czytelność jest już jednak miejscami dyskusyjna.


public class Customer
{
    public string First { get; } = "Jon";
    public string Last { get; } = "Snow";
    public decimal Money { get; set; } = 100;
    private string[] values = new string[100];
    
    //metody
    public decimal AddMoney(decimal toAdd) => Money += toAdd;
    public void Print() => Console.WriteLine(FullName);
    //właściwości 
    public string FullName => First + " " + Last;
    //indeksator
    public string this[long id] => id >= 0 && id < values.Length ? values[id] : null;

}

Using static

W najnowszej wersji C# możemy korzystać z fleczeru, który pozwala na używanie dostępnych statycznych elementów w klasach czy Enumach.

Klasycznie:


class Program
{
    static void Main()
    {
        Console.WriteLine(Math.Sqrt(10));
        Console.WriteLine(DayOfWeek.Friday - DayOfWeek.Monday);
    }
}

C# 6:


using static System.Console;
using static System.Math;
using static System.DayOfWeek;
class Program
{
    static void Main()
    {
        WriteLine(Sqrt(10)); 
        WriteLine(Friday - Monday); 
    }
}

W ten sposób można jednak łatwo skomplikować sobie życie.

Pytanie: czy Sqrt(10) przejdzie do metody z klasy System.Math czy Program, a może wyskoczy wyjątek przy kompilacji?

using static System.Console;
using static System.Math;
using static System.DayOfWeek;

class Program
{
    static void Main()
    {
        WriteLine(Sqrt(10));
        WriteLine(Friday - Monday);
    }

    int Sqrt(int x)
    {
        return x - 3;
    }
}

Ficzer całkiem ciekawy. Z jednej strony zmniejsza ilość kodu i poprawia jego czytelność, ale z drugiej może wprowadzać pewnie niejasności w zrozumieniu kontekstu. Using static zapewne najlepiej nada się do użycia dla kilku ściśle wybranych klas, aby nie utrudnić analizy kodu.

nameof

C# 6 wprowadza również wyrażenie nameof, które zwraca stringa będącego nazwą zmiennej, właściwości, klasy czy metody:


return nameof(var1) //"var1"

return nameof(collection.Item.Index) //"Index"

Idealnie nada się do wywołania PropertyChanged, wyrzucania wyjątków z informacją o nazwie zmiennej, czy operując na typach generycznych


if (value== null) throw new ArgumentNullException(nameof(value)+ " is null :(");

Filtrowanie wyjątków

W nowej wersji języka dostaliśmy możliwość dodawania filtrów do wyjątków:


try
{
	throw new Exception("My exception");
}
catch (Exception ex) when (ex.Message == "My exception")
{
	Console.WriteLine("My exception caught");
}
catch (Exception ex) 
{
	Console.WriteLine("Other exception caught here");
}

Pozostałe zmiany:

[list] [item]nowe, bardziej przyjazne inicjalizery do słowników:

Klasycznie:


var dic = new Dictionary<string, int> {
    {"x", 1},
    {"y", 2}
};

C# 6:


var dic = new Dictionary<string, int> {
    ["x"] = "1",
    ["y"] = "2",
};

[/item][item]await w catch i finally

C# 6 pozwala już na używanie await w blokach catch i finally. Podobno Microsoft użył sporej dawki magii, aby to zaimplementować (na początku twierdzili, że się nie da), ale finalnie udało się:


try
{
    …
} 
catch(Exception e)
{
    await LogAsync(e); 
}
finally
{
    await Close();
}

[/item] [item]stworzone Extension methodAdd w kolekcji zostanie użyte przy inicjalizacji kolekcji:


namespace MyExtensions
{
    public static class DicExt
    {
        public static void Add(this Dictionary<string, int> dic, int value)
        {
            dic.Add("Number " + no.ToString(), value);
        }
    }
}

using MyExtensions

var dic = new Dictionary<string, int> { 1, 4, 10 };

[/item][item]do numerów z błędami w #pragma warning disable/restore doszedł opcjonalny prefix "CS" [/item][item]usprawniono mechanizm przeciążeń[/item] [/list]

To tyle :) Nie wszystkie zmiany są szczególnie ciekawe, a kilka z nich może nawet delikatnie utrudnić późniejszą analizę kodu, jeśli będą używane w nadmiarze i w niewłaściwy sposób. Metody, właściwości i indeksatory jako pojedyncze wyrażenie lambda - tutaj jeśli za dużo zechcemy upchać do wyrażeń, wówczas otrzymamy jednolinijkowe potwory. W przypadku using static trzeba uważać na to, aby niechcący nie pomieszać kontekstów, co innego że ficzer jest racze zbędnym bajerem w codziennym kodowaniu.

Ogólnie warto jednak odnotować, że pojawił się nareszcie Elvis, który zapewne będzie często używany przez programistów. Również zmiany we właściwościach są ciekawą opcją, podobnie jak nameof i interpolacja stringów. Pozostałe rzeczy są raczej małymi ułatwieniami, których możemy nawet nie zauważyć.

Wybrane dla Ciebie
Komentarze (22)