Ist das interne Schlüsselwort C # ein Codegeruch?

In diesem Beitrag werde ich zeigen, warum ich denke, dass das interne Schlüsselwort, wenn es auf Klassenmitglieder angewendet wird, ein Codegeruch ist, und bessere Alternativen vorschlagen.

Was ist das interne Schlüsselwort?

In C # kann das interne Schlüsselwort für eine Klasse oder ihre Mitglieder verwendet werden. Es ist einer der C # -Zugriffsmodifikatoren. Auf interne Typen oder Mitglieder kann nur innerhalb von Dateien in derselben Assembly zugegriffen werden . (C # interne Keyword-Dokumentation).

Warum brauchen wir das interne Schlüsselwort?

„Der interne Zugriff wird häufig in der komponentenbasierten Entwicklung verwendet, da eine Gruppe von Komponenten auf private Weise zusammenarbeiten kann, ohne dem Rest des Anwendungscodes ausgesetzt zu sein . Beispielsweise könnte ein Framework zum Erstellen grafischer Benutzeroberflächen Steuerungs- und Formularklassen bereitstellen, die zusammenarbeiten, indem Mitglieder mit internem Zugriff verwendet werden. Da diese Mitglieder intern sind, sind sie keinem Code ausgesetzt, der das Framework verwendet. “ (C # interne Keyword-Dokumentation)

Dies sind die Anwendungsfälle, die ich für die Verwendung des internen Schlüsselworts für ein Klassenmitglied gesehen habe:

  • Rufen Sie die private Funktion einer Klasse in derselben Assembly auf.
  • Um eine private Funktion zu testen, können Sie sie als intern markieren und die DLL über InternalsVisibleTo der Test-DLL zugänglich machen.

Beide Fälle können als Codegeruch angesehen werden, der besagt, dass diese private Funktion öffentlich sein sollte.

Sehen wir uns einige Beispiele an

Hier ist ein einfaches Beispiel. Eine Funktion in einer Klasse möchte auf eine private Funktion einer anderen Klasse zugreifen.

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

Die Lösung ist einfach - markieren Sie einfach A :: func2 als öffentlich.

Schauen wir uns ein etwas komplexeres Beispiel an:

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

Was ist das Problem? Markieren Sie einfach func2 als öffentlich wie zuvor.

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

Aber wir können nicht? B ist eine interne Klasse und kann daher nicht Teil der Signatur einer öffentlichen Funktion einer öffentlichen Klasse sein.

Das sind die Lösungen, die ich gefunden habe, geordnet nach Leichtigkeit:

  1. Markieren Sie die Funktion mit dem internen Schlüsselwort
public class A{ public void func1(){ } internal void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); } }

2. Erstellen Sie eine interne Schnittstelle

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. Extrahieren Sie A.func2 in eine andere interne Klasse und verwenden Sie es anstelle von 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. Entkoppeln Sie die Funktion von internen Klassen und machen Sie sie öffentlich. Dies hängt stark davon ab, was die Funktion mit ihren Eingängen macht. Das Entkoppeln der internen Klassen kann sehr einfach, sehr schwierig und sogar unmöglich sein (ohne das Design zu ruinieren).

Aber wir haben keine öffentlichen Klassen, wir benutzen Schnittstellen ...

Schauen wir uns ein realeres Beispiel an:

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

Mal sehen, wie die vorherigen Lösungen an dieses Beispiel angepasst sind:

  1. Funktion mit Intern markieren. Dies bedeutet, dass Sie in eine Klasse umwandeln müssen, um die Funktion aufzurufen. Dies funktioniert nur, wenn Klasse A die einzige ist, die die Schnittstelle implementiert. Dies bedeutet, dass IA in Tests nicht verspottet wird und es keine andere Produktionsklasse gibt, die IA implementiert .
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. Erstellen Sie eine interne Schnittstelle, die die öffentliche Schnittstelle erweitert.

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. Extrahieren Sie A.func2 in eine andere interne Klasse und verwenden Sie es anstelle von A.func2.

4. Entkoppeln Sie die Funktion von internen Klassen und fügen Sie sie der öffentlichen Schnittstelle hinzu.

Wir können sehen, dass das interne Schlüsselwort die einfachste Lösung ist , aber es gibt andere Lösungen, die die traditionellen Bausteine ​​von OOP verwenden: Klassen und Schnittstellen . Wir können sehen, dass die zweite Lösung - das Hinzufügen einer internen Schnittstelle - nicht viel schwieriger ist, als die Funktion mit dem internen Schlüsselwort zu markieren.

Warum nicht das interne Schlüsselwort verwenden?

Wie ich in den vorherigen Beispielen gezeigt habe, ist die Verwendung des internen Schlüsselworts die einfachste Lösung . Aber Sie werden es in Zukunft schwer haben, wenn Sie Folgendes brauchen:

  • Verschieben Sie die öffentliche Klasse A in eine andere DLL (da das interne Schlüsselwort nicht mehr für dieselbe DLL gilt).
  • Erstellen Sie eine weitere Produktionsklasse, die IA implementiert
  • Mock IA in Tests

Sie denken vielleicht: "Aber dies ist nur eine Codezeile. Ich oder jeder andere kann sie bei Bedarf leicht ändern." Jetzt haben Sie eine Codezeile, die so aussieht:

((MyClass)a).internalFunction

Wenn andere diese Funktion ebenfalls aufrufen müssen, wird diese Zeile in die DLL kopiert .

Meine Schlussfolgerung

Ich denke, ein Klassenmitglied mit dem internen Schlüsselwort zu markieren, ist ein Codegeruch . In den Beispielen, die ich oben gezeigt habe, ist es die einfachste Lösung, ABER kann in Zukunft Probleme verursachen. Das Erstellen einer internen Schnittstelle ist fast genauso einfach und expliziter.

Vergleiche mit 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?