Spis treści:

Kategoria:C#


Słowo kluczowe nameof w C# 6.0

Ikona do artykułu na temat nowości w C# 6.0 - nameof

Nowości .NET znakiem ewolucji języka

Projektowanie języka programowania nie jest czynnością prostą i oczywistą. Pisząc to nie mam na myśli analizatora leksykalnego, składniowego i generatora kodu wynikowego. Chodzi o gramatykę, składnię, reguły porozumiewania się z komputerem. Taki jest bowiem cel języka - chcemy dogadać się z komputerem. I chcemy to zrobić w jak najwygodniejszy sposób. Ci, którzy znają kilka języków z pewnością mają swoje ulubione. Dla niektórych język Visual Basic jest bardzo przystępny, dla innych zaś przegadany, pleonastycznyPrzykładem takiej pleonastycznej konstrukcji jest instrukcja INSERT INTO Tabela VALUES (wartości) w SQL Server. Są różne szkoły pisania, ale projektanci dopuścili zapis skrócony INSERT Tabela VALUES (wartości). Skoro wstawiamy, to wiadomo, że do czegoś.. Projekt języka powinien również obejmować zagadnienia związane z jego przejrzystością. Wiele jest nieczytelnych konstrukcji dopuszczanych przez języki. To, że coś da się napisać, nie oznacza, że powinniśmy to robić. Wystarczy wspomnieć o sławnej już instrukcji goto. Co więcej, dobry kompilator potrafi wykryć wiele błędów już na etapie kompilacji. W końcu, język powinien ułatwiać utrzymanie, zwłaszcza dużych, projektów. Te ostatnie stwierdzenia najlepiej uzasadniają decyzję o dodaniu słowa kluczowego nameof. Zwiększenie wykrywalności błędów na etapie kompilacji, ułatwiony refactoring i utrzymanie. Co ono robi i po co z niego korzystać? W jakich sytuacjach okaże się przydatne? Zachęcam do czytania.

Wróżki, czarownice i magiczne wartości

W wielu przypadkach konieczne staje się użycie łańcuchów znakowych oznaczających jakiś element konstrukcyjny programu, na przykład właściwość, klasę, metodę. Popatrzmy na dość często spotykany przypadek powiadamiania o wyjątku:

void Calculate(string expression, params object[] arguments)
{
    if (expression == null)
        throw new ArgumentNullException("expression");

    ...
}

To całkiem poprawny fragment kodu sprawdzający argumenty wejściowe przed wykonaniem operacji właściwych. Aby szybko zorientować się, który argument jest nieprawidłowy, musimy przekazać jakieś dodatkowe informacje - w najprostszym wydaniu samą nazwę. Program działa jak należy, ale ma pewną wadę. Jeżeli zmienimy nazwę argumentu, musimy pamiętać o zmianie stałej tekstowej. W przeciwnym razie informacje zwracane przez wyjątek będą błędne. Innym przykładem, często wykorzystywanym do zademonstrowania możliwości nameof jest system powiadamiania o zmianach właściwościW moim odczuciu wykorzystanie atrybutu CallerMemberNameAttribute, obecnym w .NET 4.5, jest wystarczająco dobrym sposobem, lepszym niż nameof.. Klasy takie można rozpoznać po interfejsie INotifyPropertyChanged, który bezpośrednio lub pośrednio implementują. Popatrzmy na poniższy przykład:

class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
               name = value;
               OnPropertyChanged("Name");
            };
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Przykład bardzo podobny. Jeżeli na jakimś etapie prac uznamy, że w klasie Person zamiast Namepowinno być FirstName i LastName, musimy pamiętać o zmianie magicznej wartości "Name".

Kolejne przykłady można mnożyć: nazwy kontrolerów i akcji w ASP.NET MVC, drzewa wyrażeń z przykładem pokazanym w artykule Dynamiczny SELECT w LINQ. Wszędzie tam zastosowanie operatora nameof pozwala wykryć błędy już na etapie kompilacji. Do tego należy dążyć. W następnym podpunkcie pokażę kilka przypadków zastosowań słowa kluczowego nameof. Warto zapoznać się z wszystkimi, bo wtedy łatwiej dostrzeżemy potencjalne możliwości.

Przykłady zastosowania słowa kluczowego nameof

Użycie nameof jest bardzo proste i nie wymaga wyjaśnień. Popatrzmy na przykłady:

namespace Namespace
{
    class Program
    {
        int field1 = 0;
        public int Property2 { get; set; }

        private void Func3() { }

        static void Main(string[] args)
        {
            int local4 = 8;

            Console.WriteLine(nameof(Int32));
            Console.WriteLine(nameof(Program.Main));
            Console.WriteLine(nameof(field1));
            Console.WriteLine(nameof(Namespace.Program.field1));
            Console.WriteLine(nameof(Property2));
            Console.WriteLine(nameof(Func3));
            Console.WriteLine(nameof(local4));
            Console.WriteLine(nameof(Program));
            //Spodziewany wynik:
            //Int32
            //Main
            //field1
            //field1
            //Property2
            //Func3
            //local4
            //Program
        }
    }
}

Suche przykłady nie zawsze przemawiają z wystarczającą skutecznością. Przyjrzyjmy się konkretnym przypadkom.

Obsługa wyjątków i logowanie

Przeanalizujmy jeszcze raz początkowy przykład obsługi wyjątków i przyjrzyjmy się zmianom:

void Calculate(string expression, params object[] arguments)
{
    if (expression == null)
        throw new ArgumentNullException(nameof(expression));
    ...
}

Zmiana drobna, ale pozwalająca wykryć literówki na etapie kompilacji. Co więcej, otrzymujemy pełne wsparcie IntelliSense podczas pisania i możliwość zmiany nazwy. Popatrzmy na przerobiony drugi przykład:

Powiadamianie o zmianach

Podobnie jak wcześniej, zmiana będzie kosmetyczna. Popatrzmy na poniższy listing:

class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
                name = value;
                OnPropertyChanged(nameof(Name));
            };
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Wrócę to tego, co już napisałem: w moim odczuciu nie jest to najlepsze rozwiązanie. W języku polskim używa się pojęcia uzusu, czyli zwyczaju, przyjętego sposobu porozumiewania się, który staje się normą właśnie na zasadzie frekwencji, rosnącej częstotliwości użycia. Nie zamierzam walczyć z tym zwyczajem, ale przedstawię inne rozwiązanie, pokazane na poniższym listingu:

class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
                name = value;
                OnPropertyChanged();
            };
        }
    }

    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName="")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Wstawienie atrybutu CallerMemberName sprawi, że kompilator automatycznie uzupełni właściwość odpowiednią nazwą. Nie jest wymagane nic innego. W dalszym ciągu można jednak przekazać konkretną wartość i będzie ona miała pierwszeństwo przed usługami kompilatora.

Drzewa wyrażeń LINQ

W wielu przypadkach podczas budowy drzew wyrażeń należy posłużyć się nazwami konkretnych składowych klasy. W jednym z artykułów opisywałem Dynamiczny SELECT w LINQ i sposób definiowania drzewa. Zainteresowanych odsyłam do powyższego artykułu. Tym razem przedstawię wersję oczyszczoną z magicznych wartości. Popatrzmy na listing:

public Icon GetIcon(int id, string column)
{
    using (DBContext context = new DBContext())
    {
        var query = from o in context.Icons
                    where o.Id == id
                    select o;

        ConstructorInfo ci = typeof(Icon).GetConstructor(new Type[] { typeof(int), typeof(System.Data.Linq.Binary) });

        var parameter = Expression.Parameter(query.ElementType, "p");
        //var idAttribute = Expression.Property(parameter, "Id");
        var idAttribute = Expression.Property(parameter, nameof(Icon.ID));
        var iconAttribute = Expression.Property(parameter, column);
        var ctor = Expression.New(ci, new Expression[] { idAttribute, iconAttribute });
        var lambda = Expression.Lambda(ctor, parameter);
        //var method = Expression.Call(typeof(Queryable), "Select", new Type[] { query.ElementType, typeof(Icon) }, query.Expression, lambda);
        var method = Expression.Call(typeof(Queryable), nameof(Queryable.Select), new Type[] { query.ElementType, typeof(Icon) }, query.Expression, lambda);
        return query.Provider.CreateQuery<Icon>(method).FirstOrDefault();
    }
}

Linie kodu z magicznymi napisami zostały przeniesione do komentarza i zastąpione nowymi, z wyrażeniem nameof. Warto też wspomnieć, że nameof można stosować do tych elementów języka, do których mamy dostęp (nie można odwołać się do prywatnej właściwości, metody, wewnętrznej klasy innej biblioteki - obowiązują zwykłe reguły dostępności).

Kontrolery i akcje MVC

Z przykrością stwierdzam, że testowana przeze mnie wersja Visual Studio 2015 (14.0.22823.1) nie obsługuje słowa kluczowego nameof. Mam nadzieję, że zostanie to niedługo poprawione i możliwe będzie zapisanie na przykład takiego kodu:

@Url.Action("Contact", "HomeController")
@Url.Action(nameof(HomeController.Contact), nameof(HomeController))

Do tego czasu trzeba pozostać przy tradycyjnych rozwiązaniach lub skorzystać z jakiegoś generatora, na przykład T4MVC.

Kontrola i podstawienie w czasie kompilacji

Warto zapamiętać, że wyrażenia nameof przekształcane są przez sam kompilator na równoważne instrukcje z magicznymi ciągami znaków. Powoduje to, że:

  • program nie skompiluje się jeżeli nazwy nie będą się zgadzały,
  • instrukcja nie wprowadza żadnego narzutu.

Wyrażenie nameof jest stałą, taką samą jak stały tekst. Możemy wobec tego stosować wszystkie inne instrukcje dostępne dla stałego tekstu, między innymi możemy zapisać tak:

nameof(Int32).Trim();

Podsumowanie

Spośród wielu nowości C# 6.0, konstrukcję nameof zakwalifikowałbym do grupy zmian nieinwazyjnych, nie powodujących zbytnich komplikacji kodu. Jeżeli ktoś modyfikował nazwy składowych obiektu używanego przez mechanizmy refleksji i zastanawiał się, czy przez przypadek czegoś nie zepsuje, z pewnością ucieszy się z nowych możliwości.

Kategoria:C#

, 2015-05-06

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !
a z innej strony - co gdybym ciąg znaków chciał mieć rozbity nie na wiersze a na kolumny? Czyli ciąg ABCD: 1. kolumna: A, 2. kolumna: B, 3. kolumna: C, 4 kolumna: D?
Ciekawy artykuł.
Czy można za pomocą EF wysłać swoje zapytanie?
Czy lepiej do tego użyć ADO.net i DataTable?