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?
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:
- 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",
};
- 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();
}
- stworzone Extension method Add 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 };
-
do numerów z błędami w #pragma warning disable/restore doszedł opcjonalny prefix "CS"
-
usprawniono mechanizm przeciążeń
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ć.
Komentarze
Prześlij komentarz