C# 6.0 NewNowość NR.2Oto trzeci wpis na temat C# 6.0

Muszę przyznać, że niektóre z tych nowych funkcji można rzeczywiście zmienić i ulepszyć pisaniem kodu.

Ostatnia jest najciekawsza bo zadaje pytania jak kod powinien być napisany.

Declaration Expression

2014-11-11: Funkcjonalność ta została przesunięta do następnej wersji. Więcej

Każdy kod w C# można podzielić na wyrażenie i na deklaracje.

Deklaracja a wyrażenie C#

Wyrażenie na przykład przypisuje wartość do zmiennej. Deklaracja na przykład tworzy zmienną.

Deklaracja a wyrażenie C#

Poniżej znajduje się kod, w którym niestety istnieje potrzeba wydzielenia deklaracji kolekcji.

public static double GetNumbersDividedBy3AndDoWeirdMath()
{
    int result = 0;

    var list = new List<int>() { 1212, 6454, 121584, 3435, 67, 43652, 343 };
    var collection = list.Where(n => n % 3 == 1).ToList();

    foreach (var item in collection)
    {
       result = result + (item /  collection.Count) ;
    }

    return result;
}

Jest ona potrzebna w trakcie wykonywania pętli foreach. Bez niej kod się nie skompiluje.

Brak deklaracji

Jak do tej pory nie było możliwości mieszania wyrażeń z deklaracjami.

W C# 6.0 jest to jednak możliwe. Oto jak w wyrażeniu pętli foreach deklaruję zmienną “collection” , do której później mogę się odwołać w trakcie wykonywania pętli.

public static double GetNumbersDividedBy3AndDoWeirdMath()
{
    int result = 0;

    var list = new List<int>() { 1212, 6454, 121584, 3435, 67, 43652, 343 };
    //var collection = list.Where(n => n % 3 == 1).ToList();

    foreach (var item in var collection = list.Where(n => n % 3 == 1).ToList())
    {
       result = result + (item /  collection.Count) ;
    }

    return result;
}

Jedyna różnica pomiędzy kodami to przestrzeń dostępu. W poprzednim zapisie zmienna collection była dostępna w każdym miejscu, w metodzie. Teraz jest ona dostępna tylko w pętli.

Istnieje też inne zastosowanie wyrażeń deklaracyjnych. (Declaraction Expression)

Oto prosta metoda, która ma za zadanie spróbować zamienić napis na tryb liczbowy decimal.

Niestety by skorzystać z metody TryParse, muszę wydzielić zmienną. Uzupełnić ją, a potem dodać ją do metody z wyrażeniem kluczowym “out”

public static decimal ParseEase(string number)
{
    var amount = 0m;
    var boolean = decimal.TryParse(number,out amount);

    return amount;
}

W C# 6.0 nie muszę pisać oddzielnej deklaracji zmiennej. Mogę zadeklarować zmienną w środku wyrażanie TryParse.

public static decimal ParseEase(string number)
{
    var boolean = decimal.TryParse(number,out var amount);

    return amount;
}

Jeśli chcę zwróć inną wartość niż wartość domyślną w metodzie Parse, wystarczy, że skorzystam z wartości logicznej, jaką zwraca ta metoda by wykonać odpowiednią akcję.

public static decimal ParseEase(string number)
{
    if (decimal.TryParse(number, out var amount))
        return amount;

    return 0m;
}

Przejdźmy dalej.

Static Statments

Pamiętasz swój pierwszy program C#. Być może był to program konsolowy “Hello World”.

Być może pamiętasz, jak frustrowało cię ciągłe pisanie “Console” to “Console” tamto.

using System;

namespace CSharp6Example
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
        }
    }
}

Z wyrażeniami statycznymi nie musisz już ciągle pisać nazwy klasy statycznej, której będziesz używać. Jak to jest pokazane poniżej.

using System.Console;

namespace CSharp6Example
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteLine("Hello World");
        }
    }
}

Wystarczy napisać using nazwę klasy statycznej i od tej pory możesz korzystać z jej metod, bez pisania jej jako przedrostka.

Ta funkcjonalność może sprawić, że kod będzie bardziej czytelny. Niestety widzę także możliwości nadużycia.

Funkcja ta działa tylko dla statycznych typów. Klasa string ma statyczne metody, ale sama nie jest całkowicie statycznym typem. Dlatego w tym wypadku to nie zadziała. Oficjalnie w C# 6.0 using może być używany tylko w  kontekście przestrzeni nazw i w kontekście tej właśnie funkcjonalności.

using System.String

Nic jednak nie stoi na przeszkodzie by napisać swoją klasę statyczną ze swoimi klasycznymi metodami.

public static class StringExtension
{
    public static string JoinAndAddSeperator(string seperator,params object[] args)
    {
        StringBuilder sb = new StringBuilder();

        foreach (var item in args)
        {
            sb.Append(item + seperator);
        }
        return sb.ToString();
    }
}

A później użyć jej w następujący sposób.

using System.Text;
using CSharp6Example.StringExtension;
using System.Console;

namespace CSharp6Example
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteLine(
                JoinAndAddSeperator(" : ",
                new object[] { 1, 22, 2, 33, 3, 44 }
                    ));

            ReadLine();
        }
    }
}

To oczyszcza trochę kod.

Console Result

Ktoś jednak mógłby zadać pytanie “Skąd ja będę wiedział w jakiej klasie ta metoda się znajduje?”.

Mogę przecież założyć, że metoda “WriteLine” może być także w klasie StringExtension. Mogę też założyć, że metoda JoinAndAddSeperator bierze się z klasy statycznej Console.

Visual Studio Najechanie myszki

Nie zapominaj jednak o tym, że w Visual Studio wystarczy najechać kursorem myszki, aby to zobaczyć. Jeśli chcesz odwiedzić kod, w którym metoda jest napisana wystarczy kliknąć przycisk F12.

Dostęp Warunkowy

Oto przykładowa metoda, która ma wyświetlić nazwę metody ukrytej w typie Action.

public static class LoggerHelper
{
    public static string GetMethodName(System.Action action)
    {
        var name = "missing";

        if (action != null && action.Method != null)
        {
            name = action.Method.Name;
        }

        return name;
    }
}

Aby dostać się do nazwy metody najpierw muszę sprawdzić, czy ona sama nie ma referencji, czyli jest nullem. Później muszę sprawdzić czy metoda nie jest bez referencji.

Jeśli wszystko się zgadza, to mogę zwrócić nazwę metody.

using System.Text;
using CSharp6Example.LoggerHelper;
using System.Console;

namespace CSharp6Example
{
    class Program
    {
        static void Main(string[] args)
        {
            GetMethodName(SomeMethod);
        }

        public static void SomeMethod()
        { }
    }
}

W C# 6.0 zamiast pisać stos if-ów możesz skorzystać z nowej funkcji, która nazywa się “Conditional Access”.

Każdy znak zapytania sprawdza, czy dane wyrażenie jest puste. Jeśli tak jest, to zwracany jest null.

public static class LoggerHelper
{
    public static string GetMethodName(System.Action action)
    {
        return action?.Method?.Name;
    }
}

Składnia wygląda dużo ładniej to trzeba przyznać, ale ja nie chcę zwracać wartości null, gdy nie ma referencji.

Na szczęście do pomocy przychodzi stare wyrażenie “??” które w przypadku pustej wartości zwróci wyrażenie po prawej.

public static class LoggerHelper
{
    public static string GetMethodName(System.Action action)
    {
       return action?.Method?.Name ?? "missing";
    }
}

W skrócie jedna linijka kodu robi to.

Dostęp Warunkowy

To jeszcze nie koniec.

Await i Try/Catch

W C# 6.0 mamy możliwość użycia async/await wewnątrz klauzur catch i finally.

W poprzedniej wersji C# nie było to możliwe, ponieważ wyrażenia async/await robią dużo skakania. Metoda się wykonuje, potem czeka, aż coś inne się wykona itp.

Od czego jest jednak kompilator, który powinien ogarnąć taki scenariusz.

Oto przykładowy kod.  Nie robi on nic, ale w końcu to tylko przykład.

public static class Operations
{
    public static async Task RunAction(Action action)
    {
        try
        {
            await SendStartedMessage();
            action();
            await SendOKMessage();
        }
        catch
        {
            await SendError();
        }
        finally
        {
            await SendDoneMessage();
        }
    }

    public static async Task SendError() { await Task.FromResult(0); }
    public static async Task SendOKMessage() { await Task.FromResult(1); }
    public static async Task SendStartedMessage() { await Task.FromResult(-1); }
    public static async Task SendDoneMessage() { await Task.FromResult(200); }
}

Jak już mówimy o bloku Catach, to warto też wspomnieć o tej funkcji.

Exception Filters

Jeśli kiedykolwiek chciałbym wyrzucać wyjątek tylko, gdy wewnętrzny wyjątek istnieje, teraz jest to możliwe bez pisania klauzuri if, jak poniżej.

try
{
    await SendStartedMessage();
    action();
    await SendOKMessage();
}
catch(Exception ex)
{
    if (ex.InnerException != null)
    {
        throw;
    }

    await SendError();
}

Teraz jest możliwe dodawanie warunków do wyrażeń Catch.

try
{
    await SendStartedMessage();
    action();
    await SendOKMessage();
}
catch(Exception ex) if (ex.InnerException != null)
{
    await SendError();
}

Kod jest bardziej przejrzysty.

Keyword : NameOf

Do C# 6.0 zostało dodane nowe słowo kluczowe “nameof”.

Często istnieje potrzeba wyrzucenia wyjątku, gdy zmienna jest pusta.

public static void DoIt(string pesel)
{
    if (pesel == null)
    {
        throw new ArgumentNullException("pesel");
    }
}

Problem polega na tym, że nazwę zmiennej trzeba napisać ręcznie. Człowiek zawsze może się pomylić.

Dlatego powstało słowo kluczowe NameOf, które zwraca nazwę zmiennej.

public static void DoIt(string pesel)
{
    if (pesel == null)
    {
        throw new ArgumentNullException(nameof(pesel));
    }
}

Czas na ostatnią funkcjonalność w C# 6.0. Ledwo działa w obecnym VS 14 CTP.

Expression Bodied Members

Spójrz na kod poniżej.

public class Point3D(int x,int y,int z)
{
    public int X => x;
    public int Y => y;
    public int Z => z;
    public double Distance => Math.Sqrt(X*X + Y*Y + Z*Z);
    public Point3D Move(int dx, int dy, int dz) => new Point3D(X + dx, Y + dy, Z + dz);
}

X,Y,Z wyglądają jak pola. W rzeczywistość są właściwościami, które są domyślnie uzupełniane wartościami z konstruktora.

Używając tego mechanizmu możemy tworzyć właściwości, które zwracają wartość na bazie bardziej złożonej logiki.

Można w ten sposób pisać metody w jednej linijce. Jednak patrząc na tą składnię, pojawiają się pytania dotyczące filozofii składni języka C#.

Miałem jednak problem z użyciem tej funkcjonalności. Zapewne dlatego, że ono dopiero raczkuje. Kod powyżej mi się nie kompiluje. Jedyne co działa obecnie w CTP, to kod poniżej.

public class Point3D
{
    public int X => 15;
    public int Y => 22;
    public int Z => 33;
    public double Distance => Math.Sqrt(X*X + Y*Y + Z*Z);
}

Kompilator nie widzi wartości z domyślnego konstruktora.

Expression Bodied Members vs DefaultConstrutors

Normalny konstruktor nie może uzupełnić takiej właściwości, ponieważ “zapewne” są one tylko do odczytu, ale nie chce mi się wierzyć, bo to nie powinno tak działać.

Expression Bodied Members vs Normal Construtor

Przynajmniej widać, że wyrażenia te jakby dziwnie nie wygalały, to rzeczywiście tworzą właściwości.

Expression Bodied Members Properties

Podsumowanie

Aktualnie są to najciekawsze możliwości języka C# 6.0.

Jednak do jego premiery jeszcze daleko, dlatego przygoda nie kończy się na tym wpisie.