Вътрешната ключова дума C # има ли миризма на код?

В тази публикация ще покажа защо мисля, че вътрешната ключова дума, когато се поставя на членовете на класа, е миризма на код и предлагам по-добри алтернативи.

Каква е вътрешната ключова дума?

В C # вътрешната ключова дума може да се използва за клас или негови членове. Това е един от модификаторите за достъп на C #. Вътрешните типове или членове са достъпни само в рамките на файлове в същия сбор . (Вътрешна документация за ключови думи на C #).

Защо се нуждаем от вътрешната ключова дума?

„Честото използване на вътрешния достъп е при разработката, базирана на компоненти, тъй като дава възможност на група компоненти да си сътрудничат в частен мащаб, без да бъдат изложени на останалата част от кода на приложението . Например, рамка за изграждане на графични потребителски интерфейси може да осигури класове Control и Form, които си сътрудничат чрез използване на членове с вътрешен достъп. Тъй като тези членове са вътрешни, те не са изложени на код, който използва рамката. " (Вътрешна документация за ключови думи на C #)

Това са случаите на използване, които видях за използването на вътрешната ключова дума за член на класа:

  • Извикайте частната функция на класа в рамките на същия монтаж.
  • За да тествате частна функция, можете да я маркирате като вътрешна и да изложите DLL на тестовата DLL чрез InternalsVisibleTo.

И двата случая могат да се разглеждат като мирис на код, като се казва, че тази частна функция трябва да бъде публична.

Нека да видим няколко примера

Ето един прост пример. функция от един клас иска да получи достъп до частна функция от друг клас.

class A{ public void func1(){ func2(); } private void func2(){} } class B{ public void func(A a){ a.func2(); //Compilation error 'A.func2()' is inaccessible due to its protection level } }

Решението е просто - просто маркирайте A :: func2 като обществен.

Нека разгледаме малко по-сложен пример:

public class A{ public void func1(){} private void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); //Compilation error 'A.func2(B)' is inaccessible due to its protection level } }

Какъв е проблема? просто маркирайте func2 като обществен, както го правехме преди.

public class A{ public void func1(){ ... } public void func2(B b){ ...} // Compilation error: Inconsistent accessibility: parameter type 'B' is less accessible than method 'A.func2(B)' } internal class B{ public void func3(A a){ a.func2(this); } }

Но не можем? B е вътрешен клас, така че не може да бъде част от подписа на публична функция на публичен клас.

Това са решенията, които намерих, подредени по лекота:

  1. Маркирайте функцията с вътрешната ключова дума
public class A{ public void func1(){ } internal void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); } }

2. Създайте вътрешен интерфейс

internal interface IA2{ void func2(B b); } public class A:IA2{ public void func1(){ var b = new B(); b.func3(this); } void IA2.func2(B b){} //implement IA2 explicitly because func2 can't be public } internal class B{ public void func3(A a){ ((IA2)a).func2(this); //use interface instead of class to access func2 } }

3. Извлечете A.func2 в друг вътрешен клас и го използвайте вместо A.func2.

internal class C{ public void func2(B b){ //extract A:func2 to here } } public class A{ public void func1(){} private void func2(B b){ new C().func2(b); } } internal class B{ public void func3(){ //a is no longer needed new C().func2(this); //use internal class instead of private function } }

4. Отделете функцията от вътрешните класове и я направете публична. Това зависи много от това, което функцията прави със своите входове. отделянето на вътрешните класове може да бъде много лесно, много трудно и дори невъзможно (без да се разваля дизайна).

Но ние нямаме публични класове, ние използваме интерфейси ...

Нека да разгледаме някои по-реални примери:

public interface IA{ void func1(); } internal class A : IA { public void func1(){} private void func2(B b){} } internal class B{ public void func3(IA a){ a.func2(this); //Compilation error IA' does not contain a definition for 'func2' and no extension method 'func2' accepting a first argument of type 'IA' could be found } }

Нека да видим как предишните решения са адаптирани към този пример:

  1. Маркирайте функцията с Internal. това означава, че ще трябва да предавате в клас, за да извикате функцията, така че това ще работи само ако клас A е единственият, който изпълнява интерфейса , което означава, че IA не се подиграва в тестове и няма друг производствен клас, който прилага IA .
public interface IA{ void func1(); } internal class A : IA { public void func1(){} internal void func2(B b){} } internal class B{ public void func3(IA a){ ((A)a).func2(this); //cast to A in order to accses func2 } }

2. Създайте вътрешен интерфейс, който разширява публичния интерфейс.

internal interface IExtendedA : IA{ void func2(B b); } public interface IA{ void func1(); } internal class A : IExtendedA { public void func1(){} public void func2(B b){} } internal class B{ public void func3(IExtendedA a){ a.func2(this); } }

3. Извлечете A.func2 в друг вътрешен клас и го използвайте вместо A.func2.

4. Отделете функцията от вътрешните класове и я добавете към публичния интерфейс.

Виждаме, че вътрешната ключова дума е най-лесното решение , но има и други решения, използващи традиционните градивни елементи на ООП: класове и интерфейси . Виждаме, че второто решение - добавянето на вътрешен интерфейс не е много по-трудно от маркирането на функцията с вътрешната ключова дума.

Защо не използваме вътрешната ключова дума?

Както показах в предишните примери, използването на вътрешната ключова дума е най-лесното решение . Но в бъдеще ще ви е трудно, ако ще трябва:

  • Преместете публичния клас A в друга DLL (тъй като вътрешната ключова дума вече няма да се прилага за същата DLL)
  • Създайте друг производствен клас, който прилага IA
  • Подиграйте IA в тестове

Може да си помислите „Но това е само един ред код, аз или някой друг може лесно да го променя, ако е необходимо“. Сега имате един ред код, който изглежда така:

((MyClass)a).internalFunction

но ако и други ще трябва да извикат тази функция, този ред ще бъде копиран в DLL.

Заключението ми

Мисля, че маркирането на член на класа с вътрешната ключова дума е миризма на код . В примерите, които показах по-горе, това е най -лесното решение, НО може да създаде проблеми в бъдеще. Създаването на вътрешен интерфейс е почти толкова лесно и по-ясно.

Сравнете със C ++

The C++ “friend” keyword is similar to the C# internal keyword. It allows a class or a function to access private members of a class. The difference is it allows access to specific class or function and notall the classes in the same DLL. In my opinion, this is a better solution than the C# internal keyword.

Further Reading

Practical uses for the "internal" keyword in C#

Why does C# not provide the C++ style 'friend' keyword?