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.