Le mot clé interne C # est-il une odeur de code?

Dans cet article, je vais montrer pourquoi je pense que le mot-clé interne, lorsqu'il est placé sur les membres de la classe, est une odeur de code et suggère de meilleures alternatives.

Quel est le mot-clé interne?

En C #, le mot-clé interne peut être utilisé sur une classe ou ses membres. C'est l'un des modificateurs d'accès C #. Les types ou membres internes ne sont accessibles que dans les fichiers du même assembly . (Documentation des mots clés internes C #).

Pourquoi avons-nous besoin du mot-clé interne?

«Une utilisation courante de l'accès interne est le développement basé sur les composants car il permet à un groupe de composants de coopérer de manière privée sans être exposé au reste du code d'application . Par exemple, un cadre pour la création d'interfaces utilisateur graphiques pourrait fournir des classes Control et Form qui coopèrent en utilisant des membres avec un accès interne. Étant donné que ces membres sont internes, ils ne sont pas exposés au code qui utilise le framework. » (Documentation des mots clés internes C #)

Voici les cas d'utilisation que j'ai vus pour l'utilisation du mot-clé interne sur un membre de classe:

  • Appelez la fonction privée d'une classe dans le même assembly.
  • Afin de tester une fonction privée, vous pouvez la marquer comme interne et exposer la DLL à la DLL de test via InternalsVisibleTo.

Les deux cas peuvent être considérés comme une odeur de code, disant que cette fonction privée devrait être publique.

Voyons quelques exemples

Voici un exemple simple. une fonction d'une classe souhaite accéder à une fonction privée d'une autre classe.

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 } }

La solution est simple - marquez simplement A :: func2 comme public.

Regardons un exemple un peu plus complexe:

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 } }

Quel est le problème? marquez simplement func2 comme public comme nous l'avons fait auparavant.

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); } }

Mais nous ne pouvons pas?. B est une classe interne donc il ne peut pas faire partie de la signature d'une fonction publique d'une classe publique.

Voici les solutions que j'ai trouvées, ordonnées par facilité:

  1. Marquer la fonction avec le mot-clé interne
public class A{ public void func1(){ } internal void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); } }

2. Créez une interface interne

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. Extrayez A.func2 dans une autre classe interne et utilisez-la à la place de 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. Découpez la fonction des classes internes et rendez-la publique. Cela dépend beaucoup de ce que la fonction fait avec ses entrées. découpler les classes internes peut être très facile, très difficile et même impossible (sans ruiner le design).

Mais nous n'avons pas de classes publiques, nous utilisons des interfaces…

Regardons un autre exemple dans le monde réel:

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 } }

Voyons comment les solutions précédentes sont adaptées à cet exemple:

  1. Marquer la fonction avec Interne. cela signifie que vous devrez convertir en classe pour appeler la fonction, donc cela ne fonctionnera que si la classe A est la seule qui implémente l'interface , ce qui signifie que IA n'est pas simulée dans les tests et qu'il n'y a pas d'autre classe de production qui implémente 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. Créez une interface interne qui étend l'interface publique.

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. Extrayez A.func2 dans une autre classe interne et utilisez-la à la place de A.func2.

4. Découplez la fonction des classes internes et ajoutez-la à l'interface publique.

Nous pouvons voir que le mot-clé interne est la solution la plus simple , mais il existe d'autres solutions utilisant les blocs de construction traditionnels de la POO: les classes et les interfaces . Nous pouvons voir que la 2ème solution - ajouter une interface interne n'est pas beaucoup plus difficile que de marquer la fonction avec le mot-clé interne.

Pourquoi ne pas utiliser le mot-clé interne?

Comme je l'ai montré dans les exemples précédents, l'utilisation du mot-clé interne est la solution la plus simple . Mais vous allez avoir du mal à l'avenir si vous devez:

  • Déplacez la classe publique A vers une autre DLL (puisque le mot clé interne ne s'appliquera plus à la même DLL)
  • Créer une autre classe de production qui implémente IA
  • Mock IA dans les tests

Vous pensez peut-être "Mais ce n'est qu'une ligne de code, moi ou n'importe qui d'autre peut le changer facilement si nécessaire". Vous avez maintenant une ligne de code qui ressemble à ça:

((MyClass)a).internalFunction

mais si d'autres doivent également appeler cette fonction, cette ligne sera copiée-collée dans la DLL.

Ma conclusion

Je pense que marquer un membre de la classe avec le mot - clé interne est une odeur de code . Dans les exemples que j'ai montrés ci-dessus, c'est la solution la plus simple , MAIS peut causer des problèmes à l'avenir. Créer une interface interne est presque aussi simple et plus explicite.

Comparer avec 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?