C# 7.0 Na konferencji Build 2016, Microsoft wypuścił pierwszy publiczny wgląd do nowego Visual Studio 15. Po co go zainstalować? Warto go mieć by zobaczyć, co obecnie można zrobić w kolejnej iteracji języka C#. Jest to siódma iteracja C#. Pojawiło się w niej wiele ciekawych sztuczek.

Instalacja Preview Visual Studio 15 wydaje się mało inwazyjna na cały ekosystem programistyczny w moim komputerze. Wydaje się, że w każdej chwili mogę odinstalować ten edytor bez niszczenia czegokolwiek w swoim systemie operacyjnym.

Visual Studio 15

Po instalacji Visual Studio 15 oczywiście utworzyłem prosty projekt konsolowy. Aby użyć wszystkich możliwości C# 7.0 do projektu trzeba dodać odpowiednie symbole kompilacyjne.

Oto one: __DEMO__ oraz __DEMO_EXPERIMENTAL__

__DEMO__ oraz __DEMO_EXPERIMENTAL__

Mając to ustawione możesz przejść do zabawy z C# 7.

Lokalne metody/funkcje w C# 7

W C# 7.0 jest możliwe pisanie lokalnych funkcji wewnątrz metody. Z punktu widzenia obiektowego nie ma to sensu, ale pamiętajmy, że C# idzie coraz bardziej w kierunku języków funkcjonalnych, gdzie taka sztuczka wydaje się być jak najbardziej na miejscu.

class Program
{
    static void Main(string[] args)
    {
        void LocalMethod()
        {
            Console.WriteLine("lokalna metoda !");
        }

        LocalMethod();
        Console.ReadKey();
    }
}

Jakie to ma zastosowanie? Jeśli metoda ma dużo kodu, to warto ją rozbić na podmetody lokalne. Z punktu widzenia obiektowego jednak powinno się rozbijać kod na metody prywatne.

Wielkie główkowanie czy ta nowa funkcjonalność przypadkiem nie będzie kusić łamaniem zasady Single Responsibility Principle. Gdyż metoda mająca w sobie lokalne metody daje sygnał, że główna metoda robi za dużo rzeczy na raz.

Trzeba pamiętać jednak o filozofii języków funkcjonalnych, która mocno się różni od tego co znamy. To duży temat dlatego odsuwam go do osobnego wpisu. 

Binary literals i Digit separators

Mała rzecz a cieszy. W C# 7.0 można napisać liczby w postaci binarnej. Na początku piszemy 0b by zatwierdzić ten styl pisania liczby, a potem piszemy 0 i 1 określające bity naszej liczby.

int a = 0b1;
int b = 0b1001;
int c = 0b0001;
int d = 0b1111;

Jak widać przykładowo wartość bitowa 1001 daje liczbę 9. A wartość bitowa 1111 daje liczbę 15.

Binary literals

Poza tym można pisać duże liczby stosując następujące odstępy.

static void Main(string[] args)
{
    int a = 1__2__3__4__5;
    int b = 1_000_000;
}

Pattern Matching w C# 7

Pattern Matching wydaje się czymś naprawdę odjazdowym, jeśli chodzi o te nowe funkcjonalności.

var number = 11;

if (number is int x)
{
    Console.WriteLine(x);
}

Zdecydowanie będzie skracać kod w wyrażeniach if/else. Jak widać ta funkcjonalność pozwala na wydobycie wartości z mojego parametru, jeśli jest on określonym typem.

Dobrze się on kombinuje z operatorem ?

var word = "foo";
var obje = new object();

var s1 = word is string x ? x : "not a string";
var s2 = obje is string x ? x : "not a string";

Console.WriteLine(s1);
Console.WriteLine(s2);

Wyrażenia Pattern Matching mają głównie zastosowanie w instrukcjach warunkowych.

class Point 
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x; Y = y;
    }
}

class Program
{
    static void Main(string[] args)
    {
        object obj = new Point(44, 44);

        switch (obj)
        {
            case 42:
                Console.WriteLine("This is number 42");
                break;
            case string.Empty:
                Console.WriteLine("This is String.Empty");
                break;
            // ...
            case Point s:
            case Point(44, 44): // fine
                Console.WriteLine("Point 44,44");
                break;
        }
    }
}

Wydobywanie właściwości czy pól z parametru jest możliwe bo znamy jego typ, jest także możliwe użycie jego metod.

abstract class Person { }
class Programmer : Person
{
    public string WriteCode()
    {
        return "Writing Code";
    }
}
class HR : Person { }
class Manager : Person { }

class Program
{
    static void Main(string[] args)
    {
        var people = new Person[] { new Programmer(), new HR(), new Manager() };

        var organizedPeople = from person in people
                               let action = person match (
                                  case Programmer d : "Action : " + d.WriteCode()
                                  case HR c : "find new person"
                                  case * : "there is no action to that Person"
                              )
                               select new { Type = person, Action = action };

        foreach (var person in organizedPeople)
        {
            Console.WriteLine($"{person.Type.ToString()} - {person.Action}");
        }
        Console.ReadKey();
    }
}

Oto rezultat tego kodu:

Wynik

Warto przeczytać dokumentacje dla tej funkcjonalności. 

https://github.com/dotnet/roslyn/blob/features/patterns/docs/features/patterns.md

Tuples C# 7.0

Tuples są tymczasowymi obiektami służącymi do przetrzymywania  grupy danych C#. Jak do tej pory w C# nie było mocnego rozwiązania na grupowanie danych, które są ze sobą niezwiązane. Dlatego najczęściej w takich przypadkach po prostu tworzymy kolejną klasę, która będzie zwracać tę kolekcję wartości nawet, jeśli jako całość nie mają one znaczenia. 

Kto wie może ta klasa istnieje tylko dla tego jednego użycia. Może ta klasa istnieje tylko wewnątrz aplikacji i nigdy nie wychodzi na zewnątrz. Jest w pewnym sensie prywatną klasą biblioteki.

class PropertyCoordinates {
    public double Latitude {get; set;}
    public double Longitude {get; set;}
}
var myObj = new PropertyCoordinates { Latitude = 19.149706, Longitude = 77.256323};

W takim wypadku przydałby się jakiś tymczasowy obiekt, który przetrzymałby tę grupę wartości. Oczywiście użycie tablicy obiektów wydaje się być czymś bardziej idiotycznym. 

Ten problem można rozwiązać jeszcze inaczej. Przykładowo użyć parametrów metody jako informacji zwrotnej przy pomocy słowa kluczowego OUT. 

public void GetCoordinates(string address, out double latitude , out double longitude ) {  }
 
double lat, lng;
GetCoordinates(myValues, out latitude , out longitude );
Console.WriteLine($"Lat: {latitude }, Long: {longitude }");

Oczywiście ma to wiele wad. Nie można w takiej metodzie użyć słowa kluczowego async. Parametry umieszczone do metody muszą być wcześniej zdefiniowane jak jest pokazane w kodzie powyżej.

W C# 4.0 pojawił się typ generyczny Tuple.

class Program
{
    public static Tuple<double, double> GetCoordinates(string address)
    {
        return new Tuple<double, double>(12.12121, 75.11342);
    }

    static void Main(string[] args)
    {
        var latLng = GetCoordinates("adress");
        Console.WriteLine($"Lat: {latLng.Item1}, Long: {latLng.Item2}"); 
    }
}

To rozwiązanie wydaje się być prawie idealne, gdyby nie fakt, że właściwości Tuple nazywają się kolejno Item1, Item2 i tak dalej.

Tuple C#

Obecnie w Visual Studio 15 kod ten mi się nie kompiluje, ale oto jak zespół twórców C# chce rozwiązać ten problem aktualnie w C# 7.

public (double lat, double lng) GetCoordinates(string address) { }

W podobny sposób będzie można tworzyć zbiory pól.

var co = new (double lat, double lng) { lat = 0, lng = 0 };

Pomysł z metodami, które mogą zwracać grupy rezultatów może wydawać się dziwny, ale rzeczywiście są przypadki, w których to rozwiązanie może się przydać.

Build C# 7

Może : Records i Immutable Object C# 7

Rekordy zostały odrzucone w C# 6.0, mimo iż one działały i jak na razie nie ma ich w C# 7.0

image

Według konferencji Build być może zostaną one dodane.

Records C# 7.0

W C# 7.0 być może także będziemy mieli obiekty niezmienne, ale czas pokaże jak będzie naprawdę.

Create immutable objects

Na razie to wszystko co trzeba wiedzieć na temat C# 7.0.