Warum stellt C # das Schlüsselwort "friend" im C ++ - Stil nicht bereit? [geschlossen]


208

Die C ++ Freund Schlüsselwort erlaubt eine class Abezeichnen class Bals Freund. Dies ermöglicht Class Bden Zugriff auf die private/ protectedMitglieder von class A.

Ich habe nie etwas darüber gelesen, warum dies in C # (und VB.NET) weggelassen wurde. Die meisten Antworten auf diese frühere StackOverflow-Frage scheinen zu sagen, dass es ein nützlicher Teil von C ++ ist und es gute Gründe gibt, es zu verwenden. Nach meiner Erfahrung müsste ich zustimmen.

Eine andere Frage scheint mir wirklich zu sein, wie man etwas Ähnliches wie friendin einer C # -Anwendung macht. Während sich die Antworten im Allgemeinen um verschachtelte Klassen drehen, wirkt sie nicht ganz so elegant wie die Verwendung des friendSchlüsselworts.

Das ursprüngliche Buch über Entwurfsmuster verwendet es regelmäßig in seinen Beispielen.

Zusammenfassend gesagt, warum friendfehlt in C # und was ist die "beste Vorgehensweise" (oder die besten Methoden), um es in C # zu simulieren?

(Das internalSchlüsselwort ist übrigens nicht dasselbe, es ermöglicht allen Klassen in der gesamten Assembly den Zugriff auf internalMitglieder, während friendSie einer bestimmten Klasse vollständigen Zugriff auf genau eine andere Klasse gewähren können .)


5
Ich fühle mich geschützt intern ist ein guter Kompromiss ..
Gulzar Nazim

3
Es ist nicht zu verwirrend, oder? Wie ich oben sagte, verwendet das GOF-Buch es ziemlich oft in Beispielen. Für mich ist es nicht verwirrender als intern.
Ash

7
Ich kann sicherlich Szenarien sehen, in denen sie sehr nützlich sein können, wie bereits erwähnt Unit Tests. Aber möchten Sie wirklich, dass Ihre Freunde auf Ihre Privaten zugreifen?
Danswain

2
Es ist einfach zu beantworten: C # bietet "intern" als Zugriffsmodifikator, der den Zugriff auf den gesamten Code in demselben Modul / derselben Assembly gewährt. Dies beseitigt die Notwendigkeit für so etwas wie einen Freund. In Java verhält sich das Schlüsselwort "protected" ähnlich für den Zugriff aus demselben Paket.
Sellibitze

2
@sellibitze, ich erwähne die Unterschiede zu internen in der Frage. Das Problem mit Interanl ist, dass alle Klassen in der Assembly auf die internen Mitglieder zugreifen können. Dies unterbricht die Kapselung, da viele dieser Klassen möglicherweise keinen Zugriff benötigen.
Ash

Antworten:


67

Freunde in der Programmierung zu haben, wird mehr oder weniger als "schmutzig" angesehen und ist leicht zu missbrauchen. Es unterbricht die Beziehungen zwischen Klassen und untergräbt einige grundlegende Attribute einer OO-Sprache.

Davon abgesehen ist es eine nette Funktion und ich habe sie selbst oft in C ++ verwendet. und möchte es auch in C # verwenden. Aber ich wette, wegen C # 's "reinem" OOness (im Vergleich zu C ++' s Pseudo-OOness) hat MS entschieden, dass C #, weil Java kein Freund-Schlüsselwort hat, auch nicht sollte (nur ein Scherz;))

Im Ernst: Intern ist nicht so gut wie Freund, aber es erledigt den Job. Denken Sie daran, dass Sie Ihren Code nur selten über eine DLL an Entwickler von Drittanbietern verteilen. Solange Sie und Ihr Team über die internen Klassen und deren Verwendung Bescheid wissen, sollte es Ihnen gut gehen.

BEARBEITEN Lassen Sie mich klarstellen, wie das Schlüsselwort friend OOP untergräbt.

Private und geschützte Variablen und Methoden sind möglicherweise einer der wichtigsten Bestandteile von OOP. Die Idee, dass Objekte Daten oder Logik enthalten können, die nur sie verwenden können, ermöglicht es Ihnen, Ihre Implementierung von Funktionen unabhängig von Ihrer Umgebung zu schreiben - und dass Ihre Umgebung keine Statusinformationen ändern kann, für die sie nicht geeignet ist. Wenn Sie friend verwenden, koppeln Sie die Implementierungen zweier Klassen miteinander - was viel schlimmer ist, als wenn Sie nur deren Schnittstelle gekoppelt haben.


167
C # 'intern' schadet der Kapselung viel mehr als C ++ 'Freund'. Mit "Freundschaft" gibt der Besitzer explizit die Erlaubnis, wen er will. "Intern" ist ein offenes Konzept.
Nemanja Trifunovic

21
Anders sollte für die Funktionen gelobt werden, die er ausgelassen hat, nicht für die, die er aufgenommen hat.
Mehrdad Afshari

21
Ich bin nicht der Meinung, dass C #s interne Verletzung die Einkapselung verletzt. Das Gegenteil meiner Meinung nach. Sie können es verwenden, um ein gekapseltes Ökosystem innerhalb einer Baugruppe zu erstellen. Eine Reihe von Objekten kann interne Typen innerhalb dieses Ökosystems gemeinsam nutzen, die dem Rest der Anwendung nicht ausgesetzt sind. Ich verwende die Assembly-Tricks "Freund", um Unit-Test-Assemblys für diese Objekte zu erstellen.
Quibblesome

26
Das macht keinen Sinn. friendErmöglicht nicht, dass beliebige Klassen oder Funktionen auf private Mitglieder zugreifen. Damit können die spezifischen Funktionen oder Klassen, die als Freund markiert wurden, dies tun. Die Klasse, die das private Mitglied besitzt, kontrolliert den Zugriff weiterhin zu 100%. Sie könnten genauso gut argumentieren, dass öffentliche Mitglieder Methoden. Beide stellen sicher, dass die in der Klasse speziell aufgeführten Methoden Zugriff auf private Mitglieder der Klasse erhalten.
Jalf

8
Ich denke ganz im Gegenteil. Wenn eine Assembly 50 Klassen hat und 3 dieser Klassen eine Dienstprogrammklasse verwenden, besteht Ihre einzige Option heute darin, diese Dienstprogrammklasse als "intern" zu markieren. Auf diese Weise stellen Sie es nicht nur den drei Klassen zur Verfügung, sondern auch den übrigen Klassen in der Assembly. Was wäre, wenn Sie nur einen detaillierteren Zugriff bereitstellen möchten, sodass nur die drei fraglichen Klassen Zugriff auf die Utility-Klasse haben? Es macht es tatsächlich objektorientierter, weil es die Kapselung verbessert.
zumalifeguard

104

Als Randnotiz. Bei der Verwendung von friend geht es nicht darum, die Kapselung zu verletzen, sondern im Gegenteil darum, sie durchzusetzen. Wie Accessors + Mutatoren, Überladung von Operatoren, öffentliche Vererbung, Downcasting usw. wird es häufig missbraucht, aber dies bedeutet nicht, dass das Schlüsselwort keinen oder noch schlimmer einen schlechten Zweck hat.

Siehe Konrad Rudolph ‚s Nachricht in dem anderen Thread, oder wenn Sie es vorziehen , sehen Sie den entsprechenden Eintrag in der C ++ FAQ.


... und der entsprechende Eintrag in der FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+1 für "Bei der Verwendung eines Freundes geht es nicht darum, die Kapselung zu verletzen, sondern im Gegenteil darum, sie durchzusetzen "
Nawaz

2
Eine Frage der semantischen Meinung, denke ich; und vielleicht im Zusammenhang mit der genauen fraglichen Situation. Wenn Sie eine Klasse zu einer Klasse machen, erhalten friendSie Zugriff auf ALLE Ihre privaten Mitglieder, auch wenn Sie nur eines davon festlegen müssen. Es gibt ein starkes Argument dafür, dass das Bereitstellen von Feldern für eine andere Klasse, die sie nicht benötigt, als "Verletzung der Kapselung" gilt. Es gibt Stellen in C ++, an denen dies erforderlich ist (Überladen von Operatoren), aber es gibt einen Kompromiss bei der Kapselung, der sorgfältig abgewogen werden muss. Es scheint, dass die C # -Designer der Meinung waren, dass sich ein Kompromiss nicht gelohnt hat.
GrandOpener

3
Bei der Kapselung geht es darum, Invarianten zu erzwingen. Wenn wir eine Klasse als Freund einer anderen definieren, sagen wir ausdrücklich, dass die beiden Klassen in Bezug auf die Verwaltung der Invarianten der ersten Klasse stark miteinander verbunden sind. Es ist immer noch besser, die Klasse zum Freund zu machen, als alle Attribute direkt (als öffentliches Feld) oder über Setter verfügbar zu machen.
Luc Hermitte

1
Genau. Wenn Sie einen Freund verwenden möchten, dies aber nicht können, müssen Sie interne oder öffentliche Elemente verwenden, die viel offener dafür sind, von Dingen angestachelt zu werden, die dies nicht sollten. Besonders in einer Teamumgebung.
Jungtiere

50

Zur Information ist eine andere verwandte, aber nicht ganz dieselbe Sache in .NET [InternalsVisibleTo], mit der eine Assembly eine andere Assembly (z. B. eine Unit-Test-Assembly) bestimmen kann, die (effektiv) "internen" Zugriff auf Typen / Mitglieder in hat die ursprüngliche Baugruppe.


3
Guter Hinweis, da wahrscheinlich oft Leute, die einen Freund suchen, einen Weg zum Unit-Test mit der Fähigkeit finden möchten, mehr internes „Wissen“ zu haben.
SwissCoder

14

Sie sollten in der Lage sein, die gleichen Dinge zu erreichen, für die "Freund" in C ++ verwendet wird, indem Sie Schnittstellen in C # verwenden. Sie müssen explizit definieren, welche Mitglieder zwischen den beiden Klassen übergeben werden. Dies ist zusätzliche Arbeit, kann aber auch das Verständnis des Codes erleichtern.

Wenn jemand ein Beispiel für eine vernünftige Verwendung von "Freund" hat, das nicht über Schnittstellen simuliert werden kann, teilen Sie es bitte mit! Ich möchte die Unterschiede zwischen C ++ und C # besser verstehen.


Guter Punkt. Hattest du die Gelegenheit, dir die frühere SO-Frage anzuschauen (von Monoxid gestellt und oben verlinkt?). Ich würde mich interessieren, wie das am besten in C # gelöst werden kann?
Ash

Ich bin mir nicht sicher, wie ich es in C # machen soll, aber ich denke, Jon Skeets Antwort auf die Frage von Monoxid weist in die richtige Richtung. C # erlaubt verschachtelte Klassen. Ich habe jedoch nicht versucht, eine Lösung zu codieren und zu kompilieren. :)
Parappa

Ich denke, dass das Mediatormuster ein gutes Beispiel dafür ist, wo ein Freund nett wäre. Ich überarbeite meine Formulare, um einen Mediator zu haben. Ich möchte, dass mein Formular die "Ereignis" -ähnlichen Methoden im Mediator aufrufen kann, aber ich möchte nicht, dass andere Klassen diese "Ereignis" -ähnlichen Methoden aufrufen. Die Funktion "Freund" ermöglicht dem Formular den Zugriff auf die Methoden, ohne sie für alle anderen Elemente in der Assembly zu öffnen.
Vaccano

3
Das Problem beim Schnittstellenansatz des Ersetzens von 'Friend' besteht darin, dass ein Objekt eine Schnittstelle verfügbar macht. Dies bedeutet, dass jeder das Objekt in diese Schnittstelle umwandeln und Zugriff auf die Methoden haben kann! Das Scoping der Schnittstelle auf intern, geschützt oder privat funktioniert auch nicht so, als ob das funktionieren würde. Sie könnten einfach die fragliche Methode erweitern! Denken Sie an eine Mutter und ein Kind in einem Einkaufszentrum. Öffentlichkeit ist jeder auf der Welt. Intern sind nur die Leute im Einkaufszentrum. Privat ist nur die Mutter oder das Kind. "Freund" ist nur etwas zwischen Mutter und Tochter.
Mark A. Donohoe

1
Hier ist eine: stackoverflow.com/questions/5921612/… Da ich aus dem C ++ - Hintergrund komme, macht mich der Mangel an Freunden fast täglich wahnsinnig. In meinen wenigen Jahren mit C # habe ich buchstäblich Hunderte von Methoden öffentlich gemacht, bei denen sie privat und sicherer sein könnten, alles wegen des Mangels an Freunden. Persönlich denke ich, dass Freund das Wichtigste ist, das zu .NET hinzugefügt werden sollte
kaalus

14

Mit friendeinem C ++ hat der Designer eine genaue Kontrolle darüber, wem die privaten * Mitglieder ausgesetzt sind. Aber er ist gezwungen, jedes der privaten Mitglieder zu entlarven.

Mit internaleinem C # hat der Designer die genaue Kontrolle über die Gruppe der privaten Mitglieder, die er ausstellt. Offensichtlich kann er nur ein einziges privates Mitglied entlarven. Es wird jedoch allen Klassen in der Assembly ausgesetzt.

In der Regel möchte ein Designer nur einige wenige private Methoden ausgewählten wenigen anderen Klassen zur Verfügung stellen. Beispielsweise kann in einem Klassenfabrikmuster erwünscht sein, dass die Klasse C1 nur durch die Klassenfabrik CF1 instanziiert wird. Daher kann die Klasse C1 einen geschützten Konstruktor und eine Friend-Class-Factory CF1 haben.

Wie Sie sehen können, haben wir zwei Dimensionen, entlang derer die Kapselung verletzt werden kann. friendbricht es entlang einer Dimension, internaltut es entlang der anderen. Welches ist ein schlimmerer Verstoß gegen das Kapselungskonzept? Schwer zu sagen. Aber es wäre schön, beides friendund internalverfügbar zu haben . Eine gute Ergänzung zu diesen beiden wäre außerdem der dritte Typ von Schlüsselwort, der von Mitglied zu Mitglied verwendet wird (wie internal) und die Zielklasse angibt (wie friend).

* Der Kürze halber verwende ich "privat" anstelle von "privat und / oder geschützt".

- Nick


13

Tatsächlich bietet C # die Möglichkeit, dasselbe Verhalten auf reine OOP-Weise ohne spezielle Wörter zu erzielen - es handelt sich um private Schnittstellen.

Was die Frage betrifft Was ist das C # -Äquivalent eines Freundes? wurde als Duplikat zu diesem Artikel markiert und niemand schlägt dort eine wirklich gute Realisierung vor - ich werde hier auf beide Fragen eine Antwort zeigen.

Die Hauptidee war von hier aus: Was ist eine private Schnittstelle?

Angenommen, wir benötigen eine Klasse, die Instanzen anderer Klassen verwalten und spezielle Methoden für diese aufrufen kann. Wir möchten nicht die Möglichkeit geben, diese Methoden für andere Klassen aufzurufen. Dies ist genau das gleiche, was das C ++ - Schlüsselwort eines Freundes in der C ++ - Welt tut.

Ich denke, ein gutes Beispiel in der Praxis könnte das Full State Machine-Muster sein, bei dem einige Controller das aktuelle Statusobjekt aktualisieren und bei Bedarf zu einem anderen Statusobjekt wechseln.

Du könntest:

  • Der einfachste und schlechteste Weg, die Update () -Methode öffentlich zu machen - hoffe, jeder versteht, warum es schlecht ist.
  • Der nächste Weg ist, es als intern zu markieren. Es ist gut genug, wenn Sie Ihre Klassen einer anderen Assembly zuordnen, aber selbst dann kann jede Klasse in dieser Assembly jede interne Methode aufrufen.
  • Verwenden Sie eine private / geschützte Schnittstelle - und ich bin diesem Weg gefolgt.

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

Was ist mit der Vererbung?

Wir müssen die unter Da explizite Implementierungen von Schnittstellenelementen nicht als virtuell deklariert werden können , verwendete Technik verwenden und IState als geschützt markieren, um die Möglichkeit zu geben, auch von Controller abzuleiten.

Controller.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

Und schließlich ein Beispiel zum Testen der Vererbung der Klasse Controller: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

Ich hoffe, ich decke alle Fälle ab und meine Antwort war nützlich.


Dies ist eine großartige Antwort, die mehr Upvotes benötigt.
KthProg

@max_cn Dies ist eine wirklich schöne Lösung, aber ich sehe keinen Weg, dies mit spöttischen Frameworks für Unit-Tests zum Laufen zu bringen. Irgendwelche Vorschläge?
Steve Land

Ich bin ein bisschen verwirrt. Wenn ich wollte, dass eine externe Klasse in Ihrem ersten Beispiel (private Schnittstelle) Update aufrufen kann, wie würde ich Controller so ändern, dass dies möglich ist?
AnthonyMonterrosa

@Steve Land, Sie können die Vererbung verwenden, wie in ControllerTest.cs gezeigt wurde. Fügen Sie alle erforderlichen öffentlichen Methoden zu einer abgeleiteten Testklasse hinzu, und Sie haben Zugriff auf alle geschützten Mitarbeiter, die Sie in der Basisklasse haben.
max_cn

@AnthonyMonterrosa, wir verstecken Update vor ALLEN externen Ressourcen. Warum möchten Sie es also mit jemandem teilen?)? Natürlich können Sie eine öffentliche Methode hinzufügen, um auf private Mitglieder zuzugreifen, aber es wird wie eine Hintertür für alle anderen Klassen sein. Wenn Sie es zu Testzwecken benötigen, verwenden Sie die Vererbung, um so etwas wie die ControllerTest-Klasse zu erstellen und dort Testfälle zu erstellen.
max_cn

9

Freund ist äußerst nützlich beim Schreiben von Unit-Tests.

Dies führt zwar zu einer geringfügigen Verschmutzung Ihrer Klassendeklaration, ist jedoch auch eine vom Compiler erzwungene Erinnerung daran, welche Tests tatsächlich den internen Status der Klasse betreffen könnten.

Eine sehr nützliche und saubere Redewendung, die ich gefunden habe, ist, wenn ich Factory-Klassen habe, die sie zu Freunden der von ihnen erstellten Elemente machen, die einen geschützten Konstruktor haben. Dies war insbesondere der Fall, als ich eine einzige Factory hatte, die für die Erstellung passender Rendering-Objekte für Report Writer-Objekte verantwortlich war und in einer bestimmten Umgebung gerendert wurde. In diesem Fall verfügen Sie über einen einzigen Wissenspunkt über die Beziehung zwischen den Report-Writer-Klassen (z. B. Bildblöcke, Layoutbänder, Seitenkopfzeilen usw.) und den entsprechenden Rendering-Objekten.


Ich wollte einen Kommentar dazu abgeben, dass "Freund" für Fabrikklassen nützlich ist, aber ich bin froh zu sehen, dass es bereits jemand geschafft hat.
Edward Robertson

9

In C # fehlt das Schlüsselwort "friend" aus demselben Grund, aus dem die deterministische Zerstörung fehlt. Durch das Ändern von Konventionen fühlen sich die Menschen schlau, als ob ihre neuen Wege den alten Wegen anderer überlegen wären. Es geht nur um Stolz.

Zu sagen, dass "Freundklassen schlecht sind", ist genauso kurzsichtig wie andere unqualifizierte Aussagen wie "Keine Gotos verwenden" oder "Linux ist besser als Windows".

Das Schlüsselwort "friend" in Kombination mit einer Proxy-Klasse ist eine hervorragende Möglichkeit, nur bestimmte Teile einer Klasse bestimmten anderen Klassen zugänglich zu machen. Eine Proxy-Klasse kann als vertrauenswürdige Barriere gegen alle anderen Klassen fungieren. "public" erlaubt kein solches Targeting, und die Verwendung von "protected", um den Effekt mit der Vererbung zu erzielen, ist umständlich, wenn es wirklich keine konzeptionelle "is a" -Beziehung gibt.


In C # fehlt die deterministische Zerstörung, da sie langsamer als die Speicherbereinigung ist. Die deterministische Zerstörung benötigt Zeit für jedes erstellte Objekt, während die Speicherbereinigung nur Zeit für die aktuell aktiven Objekte benötigt. In den meisten Fällen ist dies schneller und je kurzlebiger die Objekte im System sind, desto schneller wird die Speicherbereinigung.
Edward Robertson

1
@ Edward Robertson Die deterministische Zerstörung nimmt keine Zeit in Anspruch, es sei denn, es ist ein Destruktor definiert. In diesem Fall ist die Speicherbereinigung jedoch viel schlechter, da Sie dann einen Finalizer verwenden müssen, der langsamer und nicht deterministisch ist.
Kaalus

1
... und Speicher ist nicht die einzige Art von Ressource. Menschen tun oft so, als ob es wahr wäre.
cubuspl42

8

Sie können C ++ "Freund" mit dem C # -Schlüsselwort "intern" nähern .


3
Nah dran, aber nicht ganz da. Ich nehme an, es hängt davon ab, wie Sie Ihre Assemblys / Klassen strukturieren, aber es wäre eleganter, die Anzahl der Klassen zu minimieren, denen der Zugriff mit friend gewährt wird. Zum Beispiel sollten in einer Utility / Helper-Assembly nicht alle Klassen Zugriff auf interne Mitglieder haben.
Ash

3
@Ash: Sie müssen sich daran gewöhnen, aber sobald Sie Assemblys als den grundlegenden Baustein Ihrer Anwendungen betrachten, internalist dies viel sinnvoller und bietet einen hervorragenden Kompromiss zwischen Kapselung und Nutzen. Der Standardzugriff von Java ist jedoch noch besser.
Konrad Rudolph

7

Dies ist eigentlich kein Problem mit C #. Dies ist eine grundlegende Einschränkung in IL. C # ist dadurch eingeschränkt, ebenso wie jede andere .Net-Sprache, die überprüfbar sein soll. Diese Einschränkung umfasst auch verwaltete Klassen, die in C ++ / CLI definiert sind ( Spezifikationsabschnitt 20.5 ).

Davon abgesehen denke ich, dass Nelson eine gute Erklärung dafür hat, warum dies eine schlechte Sache ist.


7
-1. friendist keine schlechte Sache; im Gegenteil, es ist weitaus besser als internal. Und Nelsons Erklärung ist schlecht und falsch.
Nawaz

4

Hören Sie auf, sich für diese Einschränkung zu entschuldigen. Freund ist schlecht, aber intern ist gut? Sie sind dasselbe, nur dieser Freund gibt Ihnen eine genauere Kontrolle darüber, wer Zugriff haben darf und wer nicht.

Dies ist, um das Kapselungsparadigma durchzusetzen? Sie müssen also Accessor-Methoden schreiben und was nun? Wie sollen Sie alle (außer den Methoden der Klasse B) davon abhalten, diese Methoden aufzurufen? Sie können nicht, weil Sie dies auch nicht kontrollieren können, weil "Freund" fehlt.

Keine Programmiersprache ist perfekt. C # ist eine der besten Sprachen, die ich je gesehen habe, aber dumme Ausreden für fehlende Funktionen zu machen, hilft niemandem. In C ++ vermisse ich das einfache Ereignis- / Delegatensystem, die Reflexion (+ automatische De- / Serialisierung) und foreach, aber in C # vermisse ich das Überladen von Operatoren (ja, sag mir immer wieder, dass du es nicht brauchst), Standardparameter, eine Konstante Das kann nicht umgangen werden, Mehrfachvererbung (ja, sagen Sie mir immer wieder, dass Sie es nicht brauchten und Schnittstellen ein ausreichender Ersatz waren) und die Möglichkeit, eine Instanz aus dem Speicher zu löschen (nein, das ist nicht schrecklich schlecht, es sei denn, Sie sind eine Bastler)


In C # können Sie die meisten Operatoren überladen. Sie können Zuweisungsoperatoren nicht überladen, aber andererseits können Sie ||/ überschreiben, &&während Sie sie kurzschließen. Standardparameter wurden in C # 4 hinzugefügt.
CodesInChaos

2

Es gibt das InternalsVisibleToAttribute seit .Net 3, aber ich vermute, dass es nur hinzugefügt wurde, um Testbaugruppen nach dem Aufkommen von Unit-Tests zu bedienen. Ich kann nicht viele andere Gründe sehen, es zu benutzen.

Es funktioniert auf Baugruppenebene, aber es erledigt die Arbeit dort, wo es nicht intern ist. Das heißt, Sie möchten eine Assembly verteilen, möchten jedoch, dass eine andere nicht verteilte Assembly privilegierten Zugriff darauf hat.

Zu Recht müssen die Freund-Assembly einen starken Schlüssel haben, um zu vermeiden, dass jemand neben Ihrer geschützten Assembly einen vorgetäuschten Freund erstellt.


2

Ich habe viele kluge Kommentare zum Schlüsselwort "Freund" gelesen und stimme zu, was nützlich ist, aber ich denke, welches Schlüsselwort "intern" weniger nützlich ist, und beide sind immer noch schlecht für die reine OO-Programmierung.

Was wir haben? (Sprichwort über "Freund" Ich sage auch über "intern")

  • macht die Verwendung von "Freund" den Code in Bezug auf oo weniger rein?
  • Ja;

  • Wird "Freund" nicht verwendet, wird der Code besser?

  • Nein, wir müssen noch einige private Beziehungen zwischen Klassen herstellen, und wir können es nur tun, wenn wir unsere schöne Kapselung brechen, also ist es auch nicht gut, ich kann sagen, was es noch böser ist, als "Freund" zu benutzen.

Die Verwendung von friend verursacht einige lokale Probleme, die Nichtverwendung führt zu Problemen für Benutzer von Codebibliotheken.

Die Lösung für die Programmiersprache, die ich so sehe:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

Was denkst du darüber? Ich denke, es ist die häufigste und rein objektorientierte Lösung. Sie können jede Methode, die Sie auswählen, auf eine beliebige Klasse zugreifen.


Ich bin kein OOP-Wächter, aber es sieht so aus, als würde es alle Probleme mit Freunden und internen lösen. Ich würde jedoch vorschlagen, ein Attribut zu verwenden, um die Erweiterung der C # -Syntax zu vermeiden: [PublicFor Bar] void addBar (Balkenleiste) {} ... Nicht, dass ich mir sicher bin, dass dies sinnvoll ist; Ich weiß nicht, wie Attribute funktionieren oder ob sie mit der Zugänglichkeit herumspielen können (sie machen viele andere "magische" Dinge).
Grault

2

Ich werde nur die Frage "Wie" beantworten.

Hier gibt es so viele Antworten, aber ich möchte eine Art "Entwurfsmuster" vorschlagen, um dieses Merkmal zu erreichen. Ich werde einen einfachen Sprachmechanismus verwenden, der Folgendes umfasst:

  • Schnittstellen
  • Verschachtelte Klasse

Zum Beispiel haben wir 2 Hauptklassen: Student und Universität. Der Student hat einen GPA, auf den nur die Universität zugreifen darf. Hier ist der Code:

public interface IStudentFriend
{
    Student Stu { get; set; }
    double GetGPS();
}

public class Student
{
    // this is private member that I expose to friend only
    double GPS { get; set; }
    public string Name { get; set; }

    PrivateData privateData;

    public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);

    // No one can instantiate this class, but Student
    // Calling it is possible via the IStudentFriend interface
    class PrivateData : IStudentFriend
    {
        public Student Stu { get; set; }

        public PrivateData(Student stu) => Stu = stu;
        public double GetGPS() => Stu.GPS;
    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend) => friend.Register(privateData);
}

public class University
{
    var studentsFriends = new List<IStudentFriend>();

    public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);

    public void PrintAllStudentsGPS()
    {
        foreach (var stu in studentsFriends)
            Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
    }
}

public static void Main(string[] args)
{
    var Technion = new University();
    var Alex     = new Student("Alex", 98);
    var Jo       = new Student("Jo", 91);

    Alex.RegisterFriend(Technion);
    Jo.RegisterFriend(Technion);
    Technion.PrintAllStudentsGPS();

    Console.ReadLine();
}

1

Ich vermute, dass es etwas mit dem C # -Kompilierungsmodell zu tun hat - IL zu erstellen, das JIT, das das zur Laufzeit kompiliert. dh: der gleiche Grund, warum sich C # -Generika grundlegend von C ++ - Generika unterscheiden.


Ich denke, der Compiler könnte es zumindest zwischen Klassen innerhalb einer Assembly relativ leicht unterstützen. Nur eine Frage der entspannten Zugriffsprüfung für die Freundesklasse in der Zielklasse. Freunde zwischen Klassen in externen Versammlungen benötigen möglicherweise grundlegendere Änderungen an der CLR.
Ash

1

Sie können es privat halten und mithilfe von Reflection Funktionen aufrufen. Das Test-Framework kann dies tun, wenn Sie es auffordern, eine private Funktion zu testen


Es sei denn, Sie arbeiten an einer Silverlight-Anwendung.
Kaalus

1

Früher habe ich regelmäßig Freunde verwendet, und ich glaube nicht, dass dies eine Verletzung von OOP oder ein Zeichen für einen Designfehler ist. Es gibt mehrere Stellen, an denen es das effizienteste Mittel zum richtigen Zweck mit der geringsten Menge an Code ist.

Ein konkretes Beispiel ist das Erstellen von Schnittstellenbaugruppen, die eine Kommunikationsschnittstelle zu einer anderen Software bereitstellen. Im Allgemeinen gibt es einige Schwergewichtsklassen, die sich mit der Komplexität des Protokolls und den Peer-Besonderheiten befassen und ein relativ einfaches Modell zum Verbinden / Lesen / Schreiben / Weiterleiten / Trennen bereitstellen, bei dem Nachrichten und Benachrichtigungen zwischen der Client-App und der Assembly weitergeleitet werden. Diese Nachrichten / Benachrichtigungen müssen in Klassen eingeschlossen werden. Die Attribute müssen im Allgemeinen von der Protokollsoftware manipuliert werden, da sie deren Ersteller ist, aber viele Dinge müssen für die Außenwelt schreibgeschützt bleiben.

Es ist einfach albern zu erklären, dass es eine Verletzung von OOP für die Protokoll- / "Ersteller" -Klasse ist, intimen Zugriff auf alle erstellten Klassen zu haben - die Erstellerklasse musste auf dem Weg nach oben jedes Datenbit missen. Was ich als am wichtigsten empfunden habe, ist die Minimierung aller zusätzlichen BS-Codezeilen, zu denen das Modell "OOP for OOP's Sake" normalerweise führt. Zusätzliche Spaghetti machen einfach mehr Käfer.

Wissen die Leute, dass Sie das interne Schlüsselwort auf Attribut-, Eigenschafts- und Methodenebene anwenden können? Dies gilt nicht nur für die Klassendeklaration der obersten Ebene (obwohl die meisten Beispiele dies zu zeigen scheinen).

Wenn Sie eine C ++ - Klasse haben, die das Schlüsselwort friend verwendet, und dies in einer C # -Klasse emulieren möchten: 1. Deklarieren Sie die C # -Klasse als öffentlich. 2. Deklarieren Sie alle Attribute / Eigenschaften / Methoden, die in C ++ geschützt sind und somit Freunden als zugänglich sind intern in C # 3. Erstellen Sie schreibgeschützte Eigenschaften für den öffentlichen Zugriff auf alle internen Attribute und Eigenschaften

Ich bin damit einverstanden, dass es nicht zu 100% mit einem Freund identisch ist, und der Komponententest ist ein sehr wertvolles Beispiel für die Notwendigkeit eines Freundes (ebenso wie der Protokollierungscode des Protokollanalysators). Intern bietet jedoch die Belichtung für die Klassen, die Sie belichten möchten, und [InternalVisibleTo ()] übernimmt den Rest - es scheint, als wäre es speziell für Unit-Tests geboren worden.

Was Freunde angeht, "die besser sind, weil Sie explizit steuern können, welche Klassen Zugriff haben" - was zum Teufel machen überhaupt eine Gruppe verdächtiger böser Klassen in derselben Versammlung? Partitionieren Sie Ihre Baugruppen!


1

Die Freundschaft kann simuliert werden, indem Schnittstellen und Implementierungen getrennt werden. Die Idee ist: " Eine konkrete Instanz erforderlich, aber den Konstruktionszugriff dieser Instanz einschränken ".

Beispielsweise

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

Trotz der Tatsache, dass ItsMeYourFriend()nur eine öffentliche FriendKlasse darauf zugreifen kann, kann möglicherweise niemand eine konkrete Instanz der FriendKlasse erhalten. Es hat einen privaten Konstruktor, während die Factory- New()Methode eine Schnittstelle zurückgibt.

Weitere Informationen finden Sie in meinem Artikel Freunde und interne Schnittstellenmitglieder kostenlos mit Codierung für Schnittstellen .


Leider kann Freundschaft auf keine Weise simuliert werden. Siehe diese Frage: stackoverflow.com/questions/5921612/…
kaalus

1

Einige haben vorgeschlagen, dass die Dinge durch die Verwendung von Freunden außer Kontrolle geraten können. Ich würde zustimmen, aber das mindert nicht seine Nützlichkeit. Ich bin mir nicht sicher, ob ein Freund das OO-Paradigma notwendigerweise mehr verletzt, als alle Ihre Klassenmitglieder öffentlich zu machen. Sicherlich erlaubt Ihnen die Sprache, alle Ihre Mitglieder öffentlich zu machen, aber es ist ein disziplinierter Programmierer, der diese Art von Entwurfsmuster vermeidet. Ebenso würde ein disziplinierter Programmierer die Verwendung von Freunden für bestimmte Fälle reservieren, in denen dies sinnvoll ist. Ich fühle, dass interne Belichtungen in einigen Fällen zu viel sind. Warum sollte eine Klasse oder Methode für alles in der Assembly verfügbar gemacht werden?

Ich habe eine ASP.NET-Seite, die meine eigene Basisseite erbt, die wiederum System.Web.UI.Page erbt. Auf dieser Seite habe ich Code, der die Fehlerberichterstattung für Endbenutzer für die Anwendung in einer geschützten Methode behandelt

ReportError("Uh Oh!");

Jetzt habe ich ein Benutzersteuerelement, das auf der Seite enthalten ist. Ich möchte, dass das Benutzersteuerelement die Fehlerberichterstattungsmethoden auf der Seite aufrufen kann.

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

Dies ist nicht möglich, wenn die ReportError-Methode geschützt ist. Ich kann es intern machen, aber es ist jedem Code in der Assembly ausgesetzt. Ich möchte nur, dass es den UI-Elementen angezeigt wird, die Teil der aktuellen Seite sind (einschließlich untergeordneter Steuerelemente). Insbesondere möchte ich, dass meine Basissteuerungsklasse genau dieselben Fehlerberichtsmethoden definiert und einfach Methoden auf der Basisseite aufruft.

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

Ich glaube, dass so etwas wie "Freund" nützlich und in der Sprache implementiert sein könnte, ohne die Sprache weniger "OO" zu machen, vielleicht als Attribute, so dass Klassen oder Methoden Freunde bestimmter Klassen oder Methoden sein können, so dass der Entwickler sehr viel bereitstellen kann spezifischer Zugang. Vielleicht so etwas wie ... (Pseudocode)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

Im Fall meines vorherigen Beispiels haben Sie vielleicht etwas wie das Folgende (man kann die Semantik argumentieren, aber ich versuche nur, die Idee zu vermitteln):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

Aus meiner Sicht besteht für das Freund-Konzept kein größeres Risiko, als Dinge öffentlich zu machen oder öffentliche Methoden oder Eigenschaften für den Zugriff auf Mitglieder zu erstellen. Wenn überhaupt, erlaubt ein Freund ein anderes Maß an Granularität bei der Zugänglichkeit von Daten und ermöglicht es Ihnen, diese Zugänglichkeit einzugrenzen, anstatt sie mit internen oder öffentlichen zu erweitern.


0

Wenn Sie mit C ++ arbeiten und sich selbst mit dem Schlüsselwort friend finden, ist dies ein sehr starker Hinweis darauf, dass Sie ein Designproblem haben. Warum zum Teufel muss eine Klasse auf die privaten Mitglieder einer anderen Klasse zugreifen?


Genau. Ich brauche das Freund-Schlüsselwort nie. Es wird im Allgemeinen verwendet, um komplexe Wartungsprobleme zu lösen.
Edouard A.

7
Überlastung des Bedieners, jemand?
Wir sind alle Monica

3
Wie oft haben Sie einen guten Grund für eine ernsthafte Überlastung des Bedieners gefunden?
Bashmohandes

8
Ich gebe Ihnen einen sehr einfachen Fall, der Ihren Kommentar zum Designproblem vollständig entlarvt. Eltern-Kind. Ein Elternteil besitzt seine Kinder. Ein Kind in der Sammlung "Kinder" des Elternteils gehört diesem Elternteil. Der Setter der Parent-Eigenschaft für Child sollte nicht von irgendjemandem festgelegt werden können. Sie muss nur dann zugewiesen werden, wenn das Kind zur Children-Sammlung der Eltern hinzugefügt wird. tWenn es öffentlich ist, kann es jeder ändern. Intern: Jeder in dieser Versammlung. Privat? Niemand. Wenn Sie den Setter zu einem Freund des Elternteils machen, werden diese Fälle beseitigt, und Ihre Klassen bleiben getrennt, aber eng miteinander verbunden. Huzzah !!
Mark A. Donohoe

2
Es gibt viele solcher Fälle, wie zum Beispiel die meisten grundlegenden Manager / verwalteten Muster. Es gibt noch eine andere Frage zu SO und niemand hat sich einen Weg ausgedacht, wie man es ohne einen Freund macht. Ich bin mir nicht sicher, warum die Leute denken, dass eine Klasse "immer ausreichen wird". Manchmal muss mehr als eine Klasse zusammenarbeiten.
Kaalus

0

Bsd

Es wurde gesagt, dass Freunde reine OOness verletzen. Dem stimme ich zu.

Es wurde auch festgestellt, dass Freunde bei der Kapselung helfen, was ich auch zustimme.

Ich denke, Freundschaft sollte zur OO-Methodik hinzugefügt werden, aber nicht ganz so wie in C ++. Ich möchte einige Felder / Methoden haben, auf die meine Freundesklasse zugreifen kann, aber ich möchte NICHT, dass sie auf ALLE meine Felder / Methoden zugreifen. Wie im wirklichen Leben ließ ich meine Freunde auf meinen persönlichen Kühlschrank zugreifen, aber ich ließ sie nicht auf mein Bankkonto zugreifen.

Man kann das wie folgt umsetzen

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

Das wird natürlich eine Compiler-Warnung erzeugen und den Intellisense verletzen. Aber es wird die Arbeit machen.

Nebenbei bemerkt denke ich, dass ein selbstbewusster Programmierer die Testeinheit durchführen sollte, ohne auf die privaten Mitglieder zuzugreifen. Dies ist völlig außerhalb des Rahmens, aber versuchen Sie, über TDD zu lesen. Wenn Sie dies dennoch tun möchten (mit C ++ wie Freunden), versuchen Sie etwas wie

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

Sie schreiben also Ihren gesamten Code, ohne UNIT_TESTING zu definieren, und wenn Sie den Komponententest durchführen möchten, fügen Sie #define UNIT_TESTING in die erste Zeile der Datei ein (und schreiben den gesamten Code, der den Komponententest durchführt, unter #if UNIT_TESTING). Das sollte sorgfältig gehandhabt werden.

Da ich denke, dass Unit-Tests ein schlechtes Beispiel für die Verwendung von Freunden sind, würde ich ein Beispiel geben, warum ich denke, dass Freunde gut sein können. Angenommen, Sie haben ein Bremssystem (Klasse). Mit der Zeit wird das Bremssystem abgenutzt und muss renoviert werden. Nun möchten Sie, dass nur ein lizenzierter Mechaniker das Problem beheben kann. Um das Beispiel weniger trivial zu machen, würde ich sagen, dass der Mechaniker seinen persönlichen (privaten) Schraubendreher verwenden würde, um es zu reparieren. Deshalb sollte die Mechanikerklasse mit der BreakingSystem-Klasse befreundet sein.


2
Dieser FriendClass-Parameter dient nicht als Zugriffssteuerung: Sie können c.MyMethod((FriendClass) null, 5.5, 3)von jeder Klasse aus aufrufen .
Jesse McGrew

0

Die Freundschaft kann auch mit "Agenten" simuliert werden - einigen inneren Klassen. Betrachten Sie folgendes Beispiel:

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

Es könnte viel einfacher sein, wenn es nur für den Zugriff auf statische Elemente verwendet wird. Vorteile für eine solche Implementierung sind, dass alle Typen im inneren Bereich von Freundschaftsklassen deklariert sind und im Gegensatz zu Schnittstellen der Zugriff auf statische Mitglieder ermöglicht.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.