Modyfikator protected internal

Dziś podstawy języka c#. Wszyscy znają modyfikatory public, protected, private i chętnie z nich korzystają. Modyfikator protected internal jest zdecydowanie mniej popularny a scenariusze użycia jeszcze rzadziej są prawidłowo identyfikowane.

Jak sama nazwa mówi protected internal składa się z dwóch poziomów dostępności. W obrębie tego samego assembly zachowuje się jak czysty internal i mamy dostęp do pola tak jakby było one public.

Załóżmy, że projekt składa się z dwóch bibliotek. W bibliotece numer A deklarujemy następującą klasę:

namespace ClassLibrary1
{
    public class Class1
    {
        protected internal int _Field;
    }
}

Jak wspomniałem, _Field w obrębie tego samego assembly będzie zachowywał się jak internal(public) zatem jest możliwe:

namespace ClassLibrary1
{
    class Class2
    {
        void Method()
        {
            Class1 sample=new Class1();
            sample._Field = 10; // OK
        }
    }
}

Co w przypadku gdy chcemy użyć pola poza danym assembly? Gdybyśmy oznaczyli _Field jako internal wtedy w innych bibliotekach dostęp byłby niemożliwy. Modyfikator protected internal umożliwia jednak dostęp w innych bibliotekach tak jakby było to pole protected – jest zatem możliwe to tylko z poziomu klasy dziedziczącej:

namespace ConsoleApplication4
{
    class OtherAssembly:Class1
    {
        void Method()
        {
            _Field = 5;// OK
        }
    }
}

Niemożliwe jednak jest korzystanie w osobnym assembly z pola _Field jak z pola public :

namespace ConsoleApplication4
{
    class OtherAssembly1
    {
        void Method()
        {
            Class1 class1=new Class1();
            class1._Field = 5;// ZLE
        }
    }
}

Podsumowując: protected internal w tej samej bibliotece zachowuje się jak internal(public) a w innej bibliotece jak protected.

Po co nam taki wymysł? W CPP były tylko 3 modyfikatory i programiści radzili sobie doskonale. Odpowiedz jest prosta – w zdecydowanej większości przypadków użycie protected internal jest sygnałem złej architektury.

Jednym z przykładów, które udało mi się znaleźć jest implementacja pewnych pluginów. Załóżmy, że mamy bibliotekę o nazwie Engine, która odpowiada za wykorzystanie z tychże pluginów. W innych bibliotekach użytkownicy mogą je implementować. Interfejs dla tego plugin’a mógłby wyglądać następująco:

abstract class IPlugin
{
    protected internal abstract void Init(...);
}

Co nam to daje? Init może być wywołany wyłącznie w bibliotece Engine – o to nam dokładnie chodzi. Chcemy móc załadować plugin w Engine ale uniemożliwić jego wywołanie po za nim. Dozwolona jest  przez użytkownika jedynie implementacja pluginu a  inicjalizacji musi dokonać Engine.

Leave a Reply

Your email address will not be published.