poniedziałek, 14 października 2019

Atrybuty Caller.* oraz DebuggerTypeProxy

Dzisiaj kilka przydatnych atrybutów.

Zacznijmy od atrybutów które pozwalają sprawdzić kto i skąd wywołuje naszą metodę. Oczywiście ktoś może zapytać: po co nam to skoro IDE jest w stanie znaleźć wszystkie użycia danej metody. Dla większości przypadków będzie to prawda. Zdarza się jednak, że piszemy bibliotekę, która następnie dodawana jest przez różne aplikacje. W takim przypadku może się przydać nam informacja kto i skąd używa naszej metody bibliotecznej.
Do dyspozycji mamy 3 atrybuty: CallerMeemberName, CallerFilePath, CallerLineNumber. Przykładowe użycie najlepiej prezentuje ich możliwości:


    public class UsageDetails
    {
        public static void Method([CallerMemberName] string callerMemeberName = null, [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0)
        {
            Console.WriteLine(callerMemeberName);
            Console.WriteLine(callerFilePath);
            Console.WriteLine(callerLineNumber);
        }
    }


Po odpaleniu apki na ekranie ukaże się taki wynik:


Mamy więc informację z jakiego pliku (dokładna ścieżka) został wywołany kod, w jakiej metodzie (Main) oraz numer linii kodu (9).

Należy przy tym zwrócić uwagę, że parametry oznaczone atrybutem Caller- są opcjonalnymi parametrami.

Poza diagnostyką atrybut CallerMemberName może przydać się w aplikacjach używających interfejsu INotifyPropertyChanged (np. WPF). W takim przypadku nie musimy podawać nazwy właściwości jako łańcucha znaków.


    public class Customer : NotifyPropertyChanged
    {
        private string firstName;
        private string lastName;

        public string FirstName
        {
            get
            {
                return firstName;
            }
            set
            {
                firstName = value;
                OnPropertyChanged();
            }
        }

        public string LastName
        {
            get { return lastName; }
            set
            {
                lastName = value;
                OnPropertyChanged();
            }
        }
    }

    public abstract class NotifyPropertyChanged : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName]string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }



Nie musimy w takim przypadku za każdym razem podawać właściwości jako string, co też zwiększa bezpieczeństwo kodu. Robiąc refactoring nie musimy się obawiać że pominiemy któryś ze stringów.


Kolejnym ciekawym atrybutem jest DebuggerTypeProxy. Atrybut ten pozwala zmienić sposób w jaki debugger Visual Studio wyświetla obiekt. Wykorzystanie atrybutu obrazuje prosty przykład:

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public IList<Phone> Phones { get; set; } = new List<Phone>(2);
    }

    public class Phone
    {
        public string Value { get; set; }
        public string Type { get; set; }
    }


Utwórzmy nowy obiekt i zobaczmy jak wygląda obiekt w debugu:





Wygląda normalnie - tak jakbyśmy tego oczekiwali. Atrybut DebuggerTypeProxy pozwala zmienić sposób wyświetlania. Dla przykładu scalmy telefony do jednej linii, a imię i nazwisko wyświetlmy jako FullName:


    public class PersonDebuggerProxy
    {
        private readonly Person obj;

        public PersonDebuggerProxy(Person obj)
        {
            this.obj = obj;
        }

        public string FullName { get => $"{obj.FirstName} {obj.LastName}";  }

        public int Age { get => obj.Age; }

        public string Phones
        {
            get
            {
                return string.Join("; ", obj.Phones?.Select(phone => $"{phone.Type} : {phone.Value}"));
            }
        }
    }

Następnie do klasy Person dodajemy deklarację atrybutu:

    [DebuggerTypeProxy(typeof(PersonDebuggerProxy))]
    public class Person


Jak teraz możecie się domyślić, obiekt zostanie wyświetlony w sposób zdefiniowany przez klasę Proxy:



Formatowanie zostało uwzględnione. Stosując atrybut DebuggerTypeProxy konstruktor klasy proxy musi przyjąć obiekt typu który chcemy formatować. Pozostałe operacje to kwestia wyobraźni i potrzeb.

Brak komentarzy:

Prześlij komentarz