sobota, 12 października 2019

C# 8.0 zmiany w interfejsie (domyślne metody, pola, modyfikatory dostępu)

Microsoft wydał niedawno C# 8.0 wraz z .NET Core 3.0. Nowości nie brakuje, większość była znana już przed premierą. Ten wpis chciałbym poświęcić domyślnej implementacji metody interfejsu. Jak każdy wie - interfejs do wersji C# 8.0 mógł posiadać tylko sygnatury metod, właściwości (properties), zdarzeń (events) lub indeksatorów (indexer).
Od wersji 8.0 interfejs może posiadać  także posiadać domyślną implementację metody jak i kilka innych nowości.

Zacznijmy od domyślnej implementacji metody. Dla programistów Javy nie jest to nowość - domyślna implementacja metody istnieje od wersji 8 Javy. Można by powiedzieć, że C# także to umożliwia od wersji 8.0 (przypadek :) ? ). Spójrzmy na przykładowy interfejs:


    public interface IFile
    {
        void SaveFile(string filePath, string fileContent);
    }


Powyższy interfejs zgodnie z nazwą powinien pozwalać operować na plikach. Obecnie posiada jedną metodę - SaveFile która pozwala zapisać plik. Jak wiadomo na plikach można wykonać znacznie więcej operacji. Od klienta przychodzi nowe zlecenie - proszę udostępnić operację odczytu pliku. Nic prostszego - stwierdzamy, że nie ma sensu męczyć osoby implementujące nasz interfejs i dodajemy do niego nową metodę z domyślną implementacją. W taki oto sposób powstaje kod:


    public interface IFile
    {
        void SaveFile(string filePath, string fileContent);
        string ReadFile(string filePath)
        {
            return File.ReadAllText(filePath);
        }
    }

    public class SystemFile : IFile
    {
        public void SaveFile(string filePath, string fileContent)
        {
            File.WriteAllText(filePath, fileContent);
        }
    }

Użycie:

            IFile file = new SystemFile();
            file.ReadFile("pathToFile");

Jeżeli używamy słowa kluczowego var podczas deklarowania typu obiektu należy pamiętać o tym, że typ SystemFile nie zawiera implementacji metody ReadFile. Mamy dwa wyjścia: jawnie zadeklarować typ jak powyżej, bądź rzutować tworzony obiekt na interfejs.

Wydawałoby się, że domyślna implementacja jest wręcz rewelacyjną wiadomością dla programistów C#. Już nie musimy martwić się, że dodając nową operację do interfejsu nasi klienci otrzymają błąd kompilacji. Ale czy tak na pewno jest?

Powyższe rozumowanie należy uzupełnić o potencjalne problemy wynikające z stosowania tego rozwiązania. Aby to pokazać dodam drugą implementację naszego interfejsu:


    public class AwsFile : IFile
    {
        public void SaveFile(string filePath, string fileContent)
        {
            var s3Client = new AmazonS3Client(RegionEndpoint.EUCentral1);
            var fileTransferUtility = new TransferUtility(s3Client);
            fileTransferUtility.Upload(filePath, "bucketName");
        }
    }

Jak widać sprawa się komplikuje. Klasa AwsFile pozwala zapisać plik w AWS.S3. Trudno sobie wyobrazić, że implementacja domyślna działająca na systemie plików pozwoli odczytać dane także w S3. Nie jest to możliwe - użytkownik prawdopodobnie otrzyma błąd. Należy więc przemyśleć czy każda domyślna implementacja ma faktycznie sens i jest na tyle uniwersalna aby ją udostępnić naszym klientom. Zauważyć można, że aż do wersji 8 nie posiadaliśmy domyślnej implementacji i doskonale sobie bez niej radziliśmy. Warto więc przemyśleć kiedy ma to sens a kiedy nie.

Oprócz powyższej zmiany interfejsy obecnie pozwalają na deklarowanie pól, statycznych sygnatur elementów interfejsu jak i stosowanie dowolnego modyfikatora dostępu (tak, tak już na rekrutacji nie trzeba bać się pytania "jaki modyfikator posiadają elementy interfejsu" :)).


    public interface IFile
    {
        void SaveFile(string filePath, string fileContent);
        string ReadFile(string filePath)
        {
            return File.ReadAllText(filePath);
        }

        private static void DoSomething()
        {

        }

        protected static void DoSomething2()
        {

        }

        private static string defaultPath;

        public static void SetDefaultPath(string path)
        {
            defaultPath = path;
        }
    }


Jak widać sporo zmian. Podsumowując od wersji 8.0 C# umożliwia następujące nowości dla interfejsów:

  • domyślna implementacja metod
  • możliwość deklaracji pól
  • statyczne elementy
  • dodawanie modyfikatorów dostępu

Brak komentarzy:

Prześlij komentarz