Jest Moc Nadszedł czas, aby poszerzyć swoją wiedzę na temat C#. Istnieje kilka ciekawych funkcjonalności, które mogą ci ułatwić pracę z kodem

Raz na jakiś czas warto odnowić swoją wiedzę.

 

1 : Słowo kluczowe Yield

Tak często korzystam ze słowa kluczowego Yield, że często zapominam, że ono w ogóle istnieje. Ma ono jednak pewne zastosowania.

Przykładowo dzięki słowu kluczowemu Yield możesz w teorii stworzyć nieskończoną kolekcję liczb losowych.

public static class YieldCollections
{
    public static IEnumerable<int> RandomNumberCollection()
    {
    
        while(true)
        {
            Random r = new Random(Guid.NewGuid()
                .GetHashCode());
    
            int number = r.Next();
    
            yield return number;
        }
    }

A potem użyć jej w taki sposób.

static void Main(string[] args)
{
    var collection = YieldCollections.
        RandomNumberCollection()
        .Take(500);

    foreach (var item in collection)
    {
        Console.Write(item + " ,");
    }

    Console.ReadLine();
}

Słowo kluczowe yield może być naprawdę potężne, jeśli użyje się go w odpowiedni sposób. Pozwala na “leniwe” generowanie sekwencji obiektów. System nie musi enumerować całej kolekcji. Może ona być tworzona w trakcie wykonywania programu na żądanie.

500 cyfr losowych

Kod metody yield jest wykonywany dopiero w pętli foreach.

2 : Async i Await

Na wzorce async i await można poświęcić oddzielny wpis jak nie cały kurs. Omówię więc je krótko. Async i await pozwalają nam w łatwy sposób nie blokować głównego wątku aplikacji.

Kod więc może wykonywać wszystkie inne polecenia, gdy w międzyczasie inny wątek czeka, przykładowo na pobranie strony internetowej bądź wykonanie zadania, które długo trwa.

Oto przykład aplikacji konsolowej. Spróbuję pobrać zawartość strony internetowej. W międzyczasie, gdy inny wątek będzie czekał na rezultat, ja w konsoli będę wyświetlał napis “…Loading…”

async static Task<string> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();
 
    Task<string> getStringTask = 
        client.GetStringAsync("http://msdn.microsoft.com");

    //Tutaj kod umieszczasz kod, który może być wykonywany
    //przed pobraniem
    int i = 0;
    while (!getStringTask.IsCompleted)
    {
        i++;
        DoIndependentWork(i);

        if (i % 7 == 0)
            Console.Clear();
        if (i == 14)
            i = 0;
    }

    string urlContents = await getStringTask;
 
    return urlContents;
}

Task reprezentuje obietnice, które w pewnym momencie w przyszłości się wykona. W pewnym miejscu w kodzie będę potrzebował rezultatu tej obietnicy, to miejsce jest oznaczone słowem await. Zanim to nastąpi mogę robić inne rzeczy.

Konsola będzie wykonywała niekończącą się pętle, aż do wykonania zadania pobierania strony.

public static void DoIndependentWork(int i)
{
    Console.ForegroundColor = (ConsoleColor)i;

    Console.WriteLine(" ... Loading ...");
    Thread.Sleep(75);
}

Gdzieś to główne polecenie musi zostać uruchomione i wyświetlone.

static  void Main(string[] args)
{
    Run();
}

public static async void Run()
{
    string a = await AccessTheWebAsync();

    Console.Clear();
    Console.ResetColor();
    Console.WriteLine(a);
}

Oto rezultat tej aplikacji. Przed pobraniem strony wyświetla się nam piękny kolorowy napisy Loading.

Async i Await exmaple

Na moim blogu mam także przykład z aplikacją WPF na temat użycia słowa kluczowego async i await.

3 : Inicjalizacja kolekcji i obiektów

Pamiętaj, że w C# łatwo jest utworzyć obiekty, tablicę i kolekcje używając specjalnej składni inicjalizującej.

public class Gamer
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Kod wyjaśnia wszystko.

Gamer gammer1 = new Gamer {
    Name = "John Smith",
    Age = 27
};

Nawet, więc jeśli klasa nie ma konstruktora w ten sposób łatwo jest uzupełnić wszystkie pola w danym obiekcie. Przydaje się do w różnych sytuacjach, przykładowo w testach jednostkowych.

int[] tab = new [] { 1, 2, 3 };
List<int> list = new List<int>()
{
    1,2,3,4,5,6,7,8,9,0
};
Dictionary<int, int> d = 
    new Dictionary<int, int>()
{
    {1,2}, {3,4}, {5,6}, {7,8}, {9,0}
};

Jeśli chcesz uzupełnić kolekcję na start nie musisz pisać deklaracji metody ADD. Możesz skorzystać z tej składni.

4 : Delegaty, funkcje i wyrażenia lambda

Na swoim blogu mam poświęcone na to oddzielne wpisy. Programowanie funkcjonalne w C# istnieje i nawet prowadzę kurs na blogu związany z tym zagadnieniem.

Co warto jednak wiedzieć o delegatach?  W C# masz do dyspozycji 2 główne delegaty generyczne. Delegata generyczyna Func służy do przyjmowania metod, które zawsze coś zwracają.

Func delgata

Ostatni więc parametr generyczny deklaruje typ zwracany przez metodę.

Action delegata

Gdy metoda nie zwraca żadnej wartości a przyjmuje dużą ilość parametrów wtedy korzystasz z delegaty generycznej Action.

Oto przykład użycia delegaty Func i Action. Każda z nich wskakuje do odpowiedniej metody zgodnej z danymi, jakie zostały podane w parametrach generycznych.

static  void Main(string[] args)
{
    Action<int> met = Program.OnePrameter;

    Func<int, string> met2 = 
        Program.OnePrameterAndReturnString;

    met.Invoke(1);
    met2.Invoke(2);

}

public static string OnePrameterAndReturnString(int a)
{
    return a.ToString();
}
public static void OnePrameter(int a)
{

}

W C# mamy także oczywiście wyrażenie lambda, które nie tylko pomaga w wyrażeniach LINQ, ale także jest esencją funkcjonalnej części języka programowania w C#.

Używając wyrażenia lambda mogę zadeklarować metody anonimowe, które są dopasowane do określonych delegatów generycznych.

Action<int> met = (a) => Console.WriteLine();

Func<int, string> met2 = (a) => a.ToString();

Action<int> met1_1 = (a) => 
{
    Console.WriteLine();
    Console.WriteLine();
};

Func<int, string> met2_1 = (a) => 
{
    a = a + 2;
    return a.ToString();
};

W teorii więc mam kod, który generuje metody i przetrzymuje w zmiennych typu delegaty generycznej Action lub Func. Jeśli chciałbyś dowiedzieć się więcej polecam kurs Funkcjonalnego programowania na moim blogu.

5 : ?? (Null coalescing operator)

Operator ?? zwraca lewą stronę wyrażenia, dopóki ona ma referencję do jakiegoś obiektu, czyli nie jest NULL. W przeciwnym wypadku zostanie zwrócona prawa strona wyrażenia.

int? nullValue = null;
int  someValue = 5;

var result = nullValue ?? someValue;

Wyrażenie to więc zwróci wartość liczbową pięć.

string a1 = null;
string a2 = null;
string a3 = "BE";
string a4 = "AA";

var res = a1 ?? a2 ?? a3;

var res2 = a3 ?? a2 ?? a1;

var res3 = a1 ?? a3 ?? a4 ?? a2;

Wyrażenie to może mieć swój własny łańcuch. Trzeba tylko pamiętać, że pierwsza wartość po lewej, która nie jest pusta zostanie zwrócona.

??

Ta składnia także pozwala na konwersje typów nullable na coś innego.

List<int?> list = 
    new List<int?> { null, 2, 3, 4, null, 5 };

int sum = list.Sum(k => k ?? 0);

Suma liczb wynosi 14, bo wartość null została zastąpiona zerem.

LINQ i rzutowanie

Warto wiedzieć, że coś takiego istnieje.

6 : $”{x}” (Ciągi interpolowane, a raczej String Interpolation) : C# 6.0

Przechodzimy więc do nowej składni C# 6.0.  C# 6.0  pozwala nam na łatwiejsze układnie napisów w jeden inny wielki napis.

object someVariableOne = "Cezary";
object someVariableTwo = "Leszek";

var bigString =
    string.Format("1 => : {0}, 2 =>: {1}", 
    someVariableOne, someVariableTwo);

//Nowe c# 6.0
var bigString2 = $"1 => : {someVariableOne}, 2 => : {someVariableTwo}";

var bigString3 = $"1 => : {someVariableOne}," +
    $" 2 => : {someVariableTwo}";

String Format zawsze był ofiarą błędów programisty więc dobrze, że powstała taka alternatywa. Nie wyjdziesz więc już poza indeks numeracji w metodzie string,Format. Określa też co wyświetlisz jako nazwy parametrów, niż jakieś cyferki, też ułatwia sprawę.

7 : ?. (Null-conditional operator) : C# 6

Sprawdzenia właściwości obiektów, czy mają one przypisane referencje jest irytujące? Tak, zwłaszcza gdy masz do dyspozycji cały łańcuch warunków do sprawdzenia, zanim dorwiesz się do wartości, która ciebie interesuje.

W C# 6.0 powstała więc alternatywa. Najpierw jednak rzucimy okiem na problem. Mam więc klasę Product, która ma właściwość klasy ProductStage, która ma właściwość ShippingStage. Ta właściwość  ma natomiast swoją właściwość  Value, które jest typem pozwalającym na wartości zerowe.

Jednym słowem na wiele sposobów gdzieś może nie być referencji do obiektu.

public class ShippingStage
{
    public int? Value { get; set; }
}

public class ProductStage
{
    public ShippingStage Shipping { get; set; }
}
public class Product
{
    public ProductStage Stage { get; set; }
}

Mam 4 obiekty repetujące produkt. Pierwszy projekt nie uzupełnia żadnej właściwości. Pozostałe obiekty uzupełniają swoje składowe mniej lub bardziej.

Product p1 = new Product();
Product p2 = new Product() { Stage = new ProductStage() };
Product p3 = new Product()
{
    Stage = new ProductStage()
    { Shipping = new ShippingStage() }
};
Product p4 = new Product()
{
    Stage = new ProductStage()
    { Shipping = new ShippingStage()
        {  Value = 2} }
};

Jak więc sprawdzić, czy mogę uzyskać, z tego obiektu wartość właściwości “Value”? Normalnie bym musiał napisać bardzo skompilowane wyrażenie warunkowe, które po kolei by sprawdziło, czy wszędzie w tym łańcuchu jest referencja do obiektu.

if (p1.Stage != null && p1.Stage.Shipping
!= null && p1.Stage.Shipping.Value != null)
    val = p1.Stage.Shipping.Value;

if (p4.Stage != null && p4.Stage.Shipping
    != null && p4.Stage.Shipping.Value != null)
    val = p4.Stage.Shipping.Value;

Samo wyrażenie nie wygląda jednak przyjaźnie. Dlatego w C# 6.0 powstała składnia, która robi to samo, tylko jest krótsza do napisania.

var value = p1?.Stage?.Shipping?.Value;
var value2 = p2?.Stage?.Shipping?.Value;
var value3 = p3?.Stage?.Shipping?.Value;
var value4 = p4?.Stage?.Shipping?.Value;

Używając wyrażenia ?. sprawdzam czy dana właściwość ma referencje do obiektu. Jeśli tak nie jest ciąg wywołań jest przerywany i do zmiennej trafia wartość null.

8 : nameof Expression : C# 6

NameOf to pomocne wyrażenie, które powstało z myślą o logowaniu błędów. Gdy logujesz błąd aplikacji opisujesz jaka zmienna do tego się przyczyniła.

private Logger _logger = new Logger();
public void ShowGamer(Gamer currentGamer)
{
    if (currentGamer == null)
        _logger.Error("Argument currentGamer is not provided");

    //...
}

public class Logger
{
    public void Error(string message) { }
}

NameOf to pomocne wyrażenie, które powstało z myślą o logowaniu błędów. Gdy logujesz błąd aplikacji opisujesz jaka zmienna do tego się przyczyniła.

public void ShowGamer(Gamer currentGamer)
{
    if (currentGamer == null)
        _logger.Error($"Argument {nameof(currentGamer)} is not provided");

    //...
}

Kod także działa dla zmiennych nie tylko dla parametrów przesyłanych do metody.

string stefan = "";
string na = nameof(stefan);

9 : Inicjalizacja Właściwości : C# 6

Jak nadać domyślą wartość dla właściwości w konstruktorze? A może stworzyć pole prywatne, do którego właściwość będzie się odwoływać?

W C# 6.0 wystarczy napisać zwykłe równa się.

public class TShirt
{
    public Guid Id { get; } = Guid.NewGuid();
    public decimal Price { get; set;} = 20.00M;
    // ...
}

Co ciekawe korzystając z tej składni nie musisz deklarować settera. Oznacza to, że właściwość ID ustawiona raz nie może być potem już zmieniona nawet przez klasę

10 : Operatory as i is

Nieważne na jakim etapie znajomości C# jesteś, znajomość operatorów as i is jest obowiązkowa. Mamy więc do dyspozycji dwie klasy, które dziedziczą po klasie abstrakcyjnej Employee.

public abstract class Employee { }
public class HR : Employee { }

public class Progammer : Employee { }

Operator is pozwala na sprawdzenie, czym dokładnie jest dana zmienna schowana za definicją Employee. Wyrażenie zwraca prawdę, gdy typ się zgadza.

Employee emp = new Programmer();
Employee emp2 = new HR();

if (emp is HR)
    Console.WriteLine("HR");
if (emp2 is Programmer)
    Console.WriteLine("Programmer");

Alternatywnie używając wyrażenia “as” mogę rzutować obiekt. Operacja ta jednak zwróci “Null” , gdy typ będzie niezgodny.

var cool = emp as HR;
var cool2 = emp as Programmer;

var h = cool ?? new HR();
var g = cool2 ?? new Programmer();

Oto wynik kodu.

Operator AS I IS

To by było na tyle, pamiętaj aby co jakiś czas odświeżać swoją wiedzę z danego języka programowania.