Perché C # non fornisce la parola chiave "amico" in stile C ++? [chiuso]


208

La parola chiave dell'amico C ++ consente class Adi designare class Bcome suo amico. Ciò consente Class Bdi accedere ai private/ protectedmembri di class A.

Non ho mai letto nulla sul motivo per cui questo è stato lasciato fuori da C # (e VB.NET). La maggior parte delle risposte a questa precedente domanda StackOverflow sembra dire che sia una parte utile di C ++ e ci sono buoni motivi per usarla. Nella mia esperienza dovrei essere d'accordo.

Un'altra domanda mi sembra davvero chiedermi come fare qualcosa di simile a friendin un'applicazione C #. Mentre le risposte generalmente ruotano attorno a classi nidificate, non sembra elegante come usare la friendparola chiave.

Il libro Design Patterns originale lo utilizza regolarmente nei suoi esempi.

Quindi, in sintesi, perché friendmanca in C # e qual è il modo (o i modi) di "best practice" di simularlo in C #?

(A proposito, la internalparola chiave è non è la stessa cosa, permette tutte le classi all'interno l'intero gruppo di accesso internalmembri, mentre friendti permette di dare una certa classe accesso completo a esattamente un altra classe)


5
mi sento protetto interno è un buon compromesso ..
Gulzar Nazim

3
Non è troppo confuso, vero? Come ho detto sopra, il libro del GOF lo usa abbastanza spesso negli esempi. Per me non è più confuso che interno.
Ash,

7
Posso certamente vedere scenari in cui possono essere molto utili, come già detto Unit Testing. Ma vuoi davvero che i tuoi amici accedano ai tuoi privati?
danswain,

2
È facile rispondere: C # offre un modificatore di accesso "interno" che consente di accedere a tutto il codice nello stesso modulo / assieme. Questo elimina la necessità di qualcosa come amico. In Java la parola chiave "protetta" si comporta in modo simile all'accesso wrt dallo stesso pacchetto.
sellibitze,

2
@sellibitze, menziono le differenze con interno nella domanda. Il problema con Interanl è che tutte le classi nell'assembly possono accedere ai membri interni. Ciò interrompe l'incapsulamento poiché molti di questi corsi potrebbero non aver bisogno di accesso.
Ash,

Risposte:


67

Avere amici in programmazione è più o meno considerato "sporco" e facile da abusare. Rompe le relazioni tra le classi e mina alcuni attributi fondamentali di un linguaggio OO.

Detto questo, è una bella funzionalità e l'ho usata molte volte in C ++; e vorrei usarlo anche in C #. Ma scommetto a causa della "pura" OOness di C # (rispetto alla pseudo OOness di C ++) MS ha deciso che, poiché Java non ha una parola chiave per gli amici, C # non dovrebbe neanche (solo scherzando;))

Una nota seria: interno non è buono come un amico ma fa il lavoro. Ricorda che è raro che distribuirai il tuo codice a sviluppatori di terze parti non tramite una DLL; quindi fintanto che tu e il tuo team siete a conoscenza delle classi interne e del loro utilizzo, dovreste andare bene.

MODIFICA Vorrei chiarire in che modo la parola chiave friend compromette OOP.

Le variabili e i metodi privati ​​e protetti sono forse una delle parti più importanti di OOP. L'idea che gli oggetti possano contenere dati o logiche che solo loro possono utilizzare consente di scrivere l'implementazione della funzionalità indipendentemente dal proprio ambiente e che l'ambiente non può modificare le informazioni sullo stato che non è adatto a gestire. Usando amico stai accoppiando le implementazioni di due classi insieme - il che è molto peggio che se hai appena accoppiato la loro interfaccia.


167
C # 'internal' sta effettivamente danneggiando l'incapsulamento molto più di C ++ 'friend'. Con "amicizia", ​​il proprietario dà esplicitamente il permesso a chi vuole. "Interno" è un concetto aperto.
Nemanja Trifunovic,

21
Anders dovrebbe essere elogiato per le caratteristiche che ha lasciato fuori non quelle che ha incluso.
Mehrdad Afshari,

21
Non sono d'accordo sul fatto che l'interno di C # faccia male l'incapsulamento. Il contrario secondo me. È possibile utilizzarlo per creare un ecosistema incapsulato all'interno di un assieme. Numerosi oggetti possono condividere tipi interni all'interno di questo ecosistema che non sono esposti al resto dell'applicazione. Uso i trucchi di assemblaggio "amico" per creare assemblaggi di unit test per questi oggetti.
Quibblesome

26
Questo non ha senso. friendnon consente a classi o funzioni arbitrarie di accedere a membri privati. Consente alle specifiche funzioni o classi contrassegnate come amico di farlo. La classe proprietaria del membro privato controlla ancora l'accesso al 100%. Potresti anche sostenere che i metodi dei membri pubblici. Entrambi assicurano che i metodi specificamente elencati nella classe abbiano accesso ai membri privati ​​della classe.
jalf

8
Penso esattamente il contrario. Se un assembly ha 50 classi, se 3 di quelle classi usano una classe di utilità, oggi l'unica opzione è contrassegnare quella classe di utilità come "interna". In tal modo, non solo lo rendi disponibile per l'uso alle 3 classi, ma al resto delle classi nell'assembly. E se tutto ciò che volevi fare fosse fornire un accesso più fine in modo tale che solo le tre classi in questione avessero accesso alla classe di utilità. In realtà lo rende più orientato agli oggetti perché migliora l'incapsulamento.
zumalifeguard,

104

In una nota a margine. Usare l'amico non significa violare l'incapsulamento, ma al contrario si tratta di applicarlo. Come accessori + mutatori, sovraccarico degli operatori, eredità pubblica, downcast, ecc. , Viene spesso utilizzato in modo improprio, ma non significa che la parola chiave non abbia, o peggio, uno scopo negativo.

Vedi il messaggio di Konrad Rudolph nell'altro thread, o se preferisci vedere la voce relativa nelle FAQ di C ++.


... e la voce corrispondente nell'FQA
Josh Lee,

28
+1 per " Usare l'amico non significa violare l'incapsulamento, ma al contrario si tratta di applicarlo "
Nawaz,

2
Una questione di opinione semantica penso; e forse in relazione all'esatta situazione in questione. Fare una classe a friendconsente di accedere a TUTTI i tuoi membri privati, anche se devi solo lasciarne uno. C'è una forte argomentazione da fare che rendere i campi disponibili per un'altra classe che non ne ha bisogno conta come "violazione dell'incapsulamento". Ci sono luoghi in C ++ in cui è necessario (sovraccarico di operatori), ma c'è un compromesso di incapsulamento che deve essere attentamente considerato. Sembra che i designer di C # abbiano ritenuto che il compromesso non ne valesse la pena.
GrandOpener,

3
L'incapsulamento riguarda l'applicazione degli invarianti. Quando definiamo una classe come amica di un'altra, diciamo esplicitamente che le due classi sono fortemente legate alla gestione degli invarianti della prima. Fare amicizia con la classe è ancora meglio che esporre tutti gli attributi direttamente (come campo pubblico) o tramite setter.
Luc Hermitte,

1
Esattamente. Quando vuoi usare l'amico ma non puoi, sei costretto a usare l'interno o il pubblico, che sono molto più aperti a essere colpiti da cose che non dovrebbero. Soprattutto in un ambiente di squadra.
Tratteggio il

50

Per informazioni, un'altra cosa correlata ma non abbastanza simile in .NET è [InternalsVisibleTo]che consente a un assembly di designare un altro assembly (come un assembly di unit test) che (effettivamente) ha accesso "interno" a tipi / membri in l'assemblaggio originale.


3
buon suggerimento, come probabilmente le persone in cerca di amici vogliono trovare un modo per testare l'unità con la capacità di avere una "conoscenza" interna.
SwissCoder

14

Dovresti essere in grado di realizzare lo stesso tipo di cose per le quali "amico" è usato in C ++ usando le interfacce in C #. Richiede di definire esplicitamente quali membri vengono passati tra le due classi, il che è un lavoro extra ma può anche rendere più facile la comprensione del codice.

Se qualcuno ha un esempio di un uso ragionevole di "amico" che non può essere simulato utilizzando le interfacce, condividilo! Mi piacerebbe capire meglio le differenze tra C ++ e C #.


Buon punto. Hai avuto la possibilità di guardare la questione SO precedente (chiesto da monossido e linkato sopra ?. Sarei interessato nel modo migliore per risolvere questo in C #?
Ash

Non sono sicuro di come farlo in C #, ma penso che la risposta di Jon Skeet ai punti interrogativi del monossido sia nella giusta direzione. C # consente le classi nidificate. In realtà non ho provato a codificare e compilare una soluzione. :)
Parappa,

Sento che il modello del mediatore è un buon esempio di dove sarebbe bello un amico. Rifatto i miei moduli per avere un mediatore. Voglio che il mio modulo sia in grado di chiamare "metodi" come metodi nel mediatore, ma non voglio davvero che altre classi chiamino quei metodi "eventi" come. La funzione "Amico" darebbe al modulo l'accesso ai metodi senza aprirlo a tutto il resto nell'assemblaggio.
Vaccano,

3
Il problema con l'approccio dell'interfaccia di sostituzione di "Friend" è che un oggetto espone un'interfaccia, il che significa che chiunque può lanciare l'oggetto su quell'interfaccia e avere accesso ai metodi! L'ambito di applicazione dell'interfaccia su interno, protetto o privato non funziona neanche come se funzionasse, si potrebbe semplicemente considerare il metodo in questione! Pensa a una madre e un bambino in un centro commerciale. Il pubblico è tutto nel mondo. Gli interni sono solo le persone nel centro commerciale. Privato è solo la madre o il bambino. "Amico" è qualcosa tra madre e figlia.
Mark A. Donohoe,

1
Eccone uno: stackoverflow.com/questions/5921612/… Dato che provengo dallo sfondo C ++, la mancanza di amici mi fa impazzire quasi ogni giorno. Nei miei pochi anni con C # ho letteralmente reso pubbliche centinaia di metodi in cui potevano essere privati ​​e più sicuri, tutto per mancanza di amici. Personalmente penso che l'amico sia la cosa più importante che dovrebbe essere aggiunta a .NET
kaalus il

14

Con friendun designer C ++ ha un controllo preciso su chi sono esposti i membri privati ​​*. Ma è costretto a esporre tutti i membri privati.

Con internalun designer C # ha un controllo preciso sul set di membri privati ​​che sta esponendo. Ovviamente, può esporre solo un singolo membro privato. Ma verrà esposto a tutte le classi nell'assemblea.

In genere, un designer desidera esporre solo alcuni metodi privati ​​a poche altre classi selezionate. Ad esempio, in un modello di fabbrica di classe può essere desiderato che la classe C1 sia istanziata solo dalla fabbrica di classe CF1. Pertanto la classe C1 può avere un costruttore protetto e una fabbrica di classe amici CF1.

Come puoi vedere, abbiamo 2 dimensioni lungo le quali l'incapsulamento può essere violato. friendlo infrange lungo una dimensione, lo internalfa lungo l'altra. Qual è una violazione peggiore del concetto di incapsulamento? Difficile da dire. Ma sarebbe bello avere entrambi friende internaldisponibili. Inoltre, una buona aggiunta a questi due sarebbe il terzo tipo di parola chiave, che verrebbe utilizzata su base membro per membro (come internal) e specifica la classe target (come friend).

* Per brevità userò "privato" anziché "privato e / o protetto".

- Nick


13

In effetti, C # offre la possibilità di ottenere lo stesso comportamento in modo OOP puro senza parole speciali: sono interfacce private.

Per quanto riguarda la domanda Qual è l'equivalente in C # dell'amico? è stato contrassegnato come duplicato di questo articolo e nessuno qui propone una realizzazione davvero buona - mostrerò la risposta su entrambe le domande qui.

L'idea principale è stata presa da qui: che cos'è un'interfaccia privata?

Diciamo, abbiamo bisogno di una classe che possa gestire istanze di un'altra classe e chiamare alcuni metodi speciali su di esse. Non vogliamo dare la possibilità di chiamare questo metodo ad altre classi. Questa è esattamente la stessa cosa che fa la parola chiave c ++ dell'amico nel mondo c ++.

Penso che un buon esempio nella pratica reale potrebbe essere il modello Full State Machine in cui alcuni controller aggiornano l'oggetto stato corrente e passano a un altro oggetto stato quando necessario.

Potresti:

  • Il modo più semplice e peggiore per rendere pubblico il metodo Update () - spero che tutti capiscano perché è male.
  • Il prossimo modo è di contrassegnarlo come interno. È abbastanza buono se metti le tue classi in un altro assembly ma anche allora ogni classe in quell'assembly potrebbe chiamare ciascun metodo interno.
  • Usa l'interfaccia privata / protetta - e l'ho seguito in questo modo.

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

Bene, che dire dell'eredità?

Dobbiamo usare la tecnica descritta in Poiché le implementazioni esplicite dei membri dell'interfaccia non possono essere dichiarate virtuali e contrassegnare IState come protetto per dare la possibilità di derivare anche dal controller.

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

E infine un esempio di come testare l'ereditarietà della classe Controller: ControllerTest.cs

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

Spero di coprire tutti i casi e la mia risposta è stata utile.


Questa è una risposta fantastica che ha bisogno di più voti.
KthProg,

@max_cn Questa è una soluzione davvero interessante, ma non vedo un modo per farlo funzionare con framework di derisione per test unitari. Eventuali suggerimenti?
Steve Land,

Sono un po 'confuso. Se volessi che una classe esterna fosse in grado di chiamare Update nel tuo primo esempio (interfaccia privata), come avrei modificato il Controller per consentire che ciò accadesse?
AnthonyMonterrosa,

@Steve Land, è possibile utilizzare l'ereditarietà come mostrato in ControllerTest.cs. Aggiungi tutti i metodi pubblici necessari in una classe di test derivata e avrai accesso a tutto il personale protetto che hai nella classe base.
max_cn

@AnthonyMonterrosa, stiamo nascondendo l'aggiornamento da TUTTE le risorse esterne, quindi perché vuoi condividerlo con qualcuno;)? Sicuramente puoi aggiungere qualche metodo pubblico per accedere ai membri privati ​​ma sarà come una backdoor per qualsiasi altra classe. Ad ogni modo, se ne hai bisogno a scopo di test, usa l'ereditarietà per creare qualcosa di simile alla classe ControllerTest per fare casi di test lì.
max_cn

9

Friend è estremamente utile durante la scrittura di unit test.

Sebbene ciò comporti un costo per l'inquinamento della dichiarazione di classe leggermente, è anche un promemoria forzato dal compilatore di quali test potrebbero effettivamente interessare lo stato interno della classe.

Un idioma molto utile e pulito che ho trovato è quando ho lezioni di fabbrica, rendendole amiche degli oggetti che creano che hanno un costruttore protetto. Più specificamente, questo è stato quando ho avuto un'unica fabbrica responsabile della creazione di oggetti di rendering corrispondenti per gli oggetti del writer di report, il rendering in un determinato ambiente. In questo caso hai un solo punto di conoscenza sulla relazione tra le classi di chi scrive i report (cose come blocchi di immagini, bande di layout, intestazioni di pagina ecc.) E i loro corrispondenti oggetti di rendering.


Stavo per fare un commento sull'utilità di "amico" per le lezioni di fabbrica, ma sono felice di vedere che qualcuno l'ha già fatto.
Edward Robertson,

9

In C # manca la parola chiave "amico" per lo stesso motivo della sua distruzione deterministica mancante. Il cambiamento delle convenzioni fa sentire le persone intelligenti, come se i loro nuovi modi fossero superiori a quelli di qualcun altro. Si tratta di orgoglio.

Dire che "le classi di amici sono cattive" è miope come altre dichiarazioni non qualificate come "non usare goto" o "Linux è meglio di Windows".

La parola chiave "amico" combinata con una classe proxy è un ottimo modo per esporre solo alcune parti di una classe ad altre classi specifiche. Una classe proxy può fungere da barriera affidabile contro tutte le altre classi. "pubblico" non consente questo tipo di targeting e l'utilizzo di "protetto" per ottenere l'effetto con l'ereditarietà è imbarazzante se in realtà non esiste alcuna "concettuale" relazione.


In C # manca la distruzione deterministica perché è più lenta della garbage collection. La distruzione deterministica richiede tempo per ogni oggetto creato, mentre la garbage collection richiede solo tempo per gli oggetti attualmente attivi. Nella maggior parte dei casi, questo è più veloce e più oggetti di breve durata ci sono nel sistema, la raccolta dei rifiuti relativamente più veloce diventa.
Edward Robertson,

1
@Edward Robertson La distruzione deterministica non richiede tempo a meno che non sia definito un distruttore. Ma in questo caso, la raccolta dei rifiuti è molto peggio, perché quindi è necessario utilizzare un finalizzatore, che è più lento e non deterministico.
Kaalus,

1
... e la memoria non è l'unico tipo di risorsa. Le persone spesso si comportano come se fosse vero.
cubuspl42,

8

Puoi avvicinarti a "amico" di C ++ con la parola chiave "interno" di C # .


3
Vicino, ma non del tutto lì. Suppongo che dipenda da come strutturi le tue assemblee / classi, ma sarebbe più elegante ridurre al minimo il numero di classi a cui è stato concesso l'accesso usando l'amico. Per esempio in un assembly utility / helper, non tutte le classi dovrebbero avere accesso ai membri interni.
Ash,

3
@Ash: è necessario abituarsi ad esso, ma una volta che si vedono gli assembly come elementi costitutivi fondamentali delle applicazioni, è internalmolto più sensato e offre un eccellente compromesso tra incapsulamento e utilità. Tuttavia, l'accesso predefinito di Java è ancora migliore.
Konrad Rudolph,

7

Questo in realtà non è un problema con C #. È una limitazione fondamentale in IL. C # è limitato da questo, come qualsiasi altro linguaggio .Net che cerca di essere verificabile. Questa limitazione include anche le classi gestite definite in C ++ / CLI (specifica sezione 20.5 ).

Detto questo, penso che Nelson abbia una buona spiegazione del perché questa è una cosa negativa.


7
-1. friendnon è una brutta cosa; al contrario, è molto meglio di internal. E la spiegazione di Nelson è cattiva e scorretta.
Nawaz,

4

Smetti di inventare scuse per questa limitazione. amico è cattivo, ma interno è buono? sono la stessa cosa, solo quell'amico ti dà un controllo più preciso su chi è autorizzato ad accedere e chi non lo è.

Questo per far rispettare il paradigma dell'incapsulamento? quindi devi scrivere metodi di accesso e adesso cosa? come dovresti impedire a tutti (tranne i metodi di classe B) di chiamare questi metodi? non puoi, perché non puoi nemmeno controllarlo, a causa della mancanza di "amico".

Nessun linguaggio di programmazione è perfetto. C # è una delle migliori lingue che abbia mai visto, ma inventare stupide scuse per le funzioni mancanti non aiuta nessuno. In C ++, mi manca il semplice sistema evento / delegato, reflection (+ de / serializzazione automatica) e foreach, ma in C # mi manca il sovraccarico dell'operatore (sì, continuate a dirmi che non ne avete bisogno), parametri predefiniti, una const che non può essere eluso, eredità multipla (sì, continuate a dirmi che non ne avete bisogno e che le interfacce erano una sostituzione sufficiente) e la possibilità di decidere di eliminare un'istanza dalla memoria (no, questo non è terribilmente male a meno che non siate un riparatore)


In C # puoi sovraccaricare la maggior parte degli operatori. Non è possibile sovraccaricare gli operatori di assegnazione, ma d'altra parte è possibile ignorare ||/ &&mantenendoli in corto circuito. I parametri predefiniti sono stati aggiunti in C # 4.
CodesInChaos

2

Esiste l'InternalsVisibleToAttribute dal .Net 3 ma sospetto che l'hanno aggiunto solo per soddisfare gli assiemi dopo l'aumento dei test unitari. Non riesco a vedere molte altre ragioni per usarlo.

Funziona a livello di assieme ma fa il lavoro in cui interno no; ovvero, dove si desidera distribuire un assembly ma si desidera che un altro assembly non distribuito abbia un accesso privilegiato ad esso.

Abbastanza giustamente richiedono che l'assembly degli amici abbia una chiave forte per evitare che qualcuno crei un amico finto insieme all'assembly protetto.


2

Ho letto molti commenti intelligenti sulla parola chiave "amico" e concordo sul fatto che sia utile, ma penso che la parola chiave "interna" sia meno utile, ed entrambi sono ancora dannosi per la pura programmazione OO.

Cosa abbiamo? (dicendo di "amico" Dico anche di "interno")

  • usare "amico" rende il codice meno puro rispetto a oo?
  • sì;

  • non usare "amico" migliora il codice?

  • no, dobbiamo ancora stabilire delle relazioni private tra le classi, e possiamo farlo solo se rompiamo il nostro bellissimo incapsulamento, quindi non va bene, posso dire cosa è ancora più malvagio che usare "amico".

L'uso di friend crea alcuni problemi locali, non usarlo crea problemi per gli utenti della libreria di codici.

la buona soluzione comune per il linguaggio di programmazione la vedo così:

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

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

Cosa ne pensi? Penso che sia la soluzione più comune e pura orientata agli oggetti. Puoi aprire l'accesso a qualsiasi metodo tu scelga per qualsiasi classe desideri.


Non sono una sentinella OOP, ma sembra che risolva i problemi di tutti contro l'amico e l'interno. Ma suggerirei di usare un attributo per evitare l'estensione della sintassi di C #: [PublicFor Bar] void addBar (Bar bar) {} ... Non che sono sicuro che abbia senso; Non so come funzionano gli attributi o se possono giocherellare con l'accessibilità (fanno molte altre cose "magiche").
Grault,

2

Risponderò solo alla domanda "How".

Ci sono così tante risposte qui, tuttavia vorrei proporre una sorta di "modello di progettazione" per ottenere quella caratteristica. Userò un meccanismo di linguaggio semplice, che include:

  • interfacce
  • Classe annidata

Ad esempio abbiamo 2 classi principali: Studente e Università. Lo studente ha un GPA a cui solo l'università ha potuto accedere. Ecco il codice:

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

Ho il sospetto che abbia qualcosa a che fare con il modello di compilazione C # - costruire IL JIT compilandolo in fase di esecuzione. vale a dire: la stessa ragione per cui i generici C # sono fondamentalmente diversi dai generici C ++.


Penso che il compilatore potrebbe supportarlo ragionevolmente facilmente almeno tra le classi all'interno di un assembly. È solo una questione di rilassante controllo dell'accesso per la classe di amici nella classe target. Forse l'amico tra le classi in assemblee esterne potrebbe aver bisogno di modifiche più fondamentali al CLR.
Ash,

1

puoi tenerlo privato e usare la riflessione per chiamare le funzioni. Il framework di test può eseguire questa operazione se viene richiesto di testare una funzione privata


A meno che tu non stia lavorando su un'applicazione Silverlight.
Kaalus,

1

Usavo regolarmente un amico e non penso che ci sia alcuna violazione di OOP o un segno di un difetto di progettazione. Esistono diversi luoghi in cui è il mezzo più efficiente per il fine corretto con la minima quantità di codice.

Un esempio concreto è quando si creano assiemi di interfacce che forniscono un'interfaccia di comunicazione con altri software. Generalmente ci sono alcune classi di pesi massimi che gestiscono la complessità del protocollo e le peculiarità dei peer e forniscono un modello di connessione / lettura / scrittura / inoltro / disconnessione relativamente semplice che prevede il passaggio di messaggi e notifiche tra l'app client e l'assembly. Quei messaggi / notifiche devono essere racchiusi in classi. Gli attributi generalmente devono essere manipolati dal software di protocollo in quanto è il loro creatore, ma molte cose devono rimanere di sola lettura al mondo esterno.

È semplicemente sciocco dichiarare che è una violazione di OOP che la classe protocol / "creator" abbia un accesso intimo a tutte le classi create - la classe creator ha dovuto sottrarre ogni bit di dati durante la salita. Quello che ho trovato più importante è minimizzare tutte le righe extra di codice BS che il modello "OOP per OOP's Sake" di solito porta a. Gli spaghetti extra fanno solo più insetti.

Le persone sanno che puoi applicare la parola chiave interna a livello di attributo, proprietà e metodo? Non è solo per la dichiarazione di classe di livello superiore (anche se la maggior parte degli esempi sembra dimostrarlo.)

Se si dispone di una classe C ++ che utilizza la parola chiave friend e si desidera emularla in una classe C #: 1. dichiarare pubblica la classe C # 2. dichiarare tutti gli attributi / proprietà / metodi che sono protetti in C ++ e quindi accessibili agli amici come internal in C # 3. creare proprietà di sola lettura per l'accesso pubblico a tutti gli attributi e proprietà interni

Sono d'accordo che non sia uguale al 100% come amico, e unit test è un esempio molto prezioso della necessità di qualcosa come amico (come il codice di registrazione dell'analizzatore di protocollo). Tuttavia interno fornisce l'esposizione alle classi che si desidera avere esposizione e [InternalVisibleTo ()] gestisce il resto - sembra che sia nato appositamente per unit test.

Per quanto riguarda l'amico "stare meglio perché puoi controllare esplicitamente a quali classi hanno accesso" - cosa diavolo stanno facendo un gruppo di sospette classi malvagie nella stessa assemblea? Partiziona le tue assemblee!


1

L'amicizia può essere simulata separando interfacce e implementazioni. L'idea è: " Richiedi un'istanza concreta ma limita l'accesso alla costruzione di quell'istanza ".

Per esempio

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

A dispetto del fatto che ItsMeYourFriend()solo la Friendclasse pubblica può accedervi, dal momento che nessun altro può ottenere un'istanza concreta della Friendclasse. Ha un costruttore privato, mentre il New()metodo factory restituisce un'interfaccia.

Vedi il mio articolo Amici e membri dell'interfaccia interna a costo zero con la codifica delle interfacce per i dettagli.


Purtroppo l'amicizia non può essere simulata in alcun modo possibile. Vedere questa domanda: stackoverflow.com/questions/5921612/...
kaalus

1

Alcuni hanno suggerito che le cose possono sfuggire al controllo usando l'amico. Sono d'accordo, ma ciò non diminuisce la sua utilità. Non sono sicuro che un amico danneggi necessariamente il paradigma OO più che rendere pubblici tutti i membri della tua classe. Certamente la lingua ti permetterà di rendere pubblici tutti i tuoi membri, ma è un programmatore disciplinato che evita quel tipo di modello di progettazione. Allo stesso modo un programmatore disciplinato riserverebbe l'uso dell'amico per casi specifici in cui ha senso. Sento che l'interno espone troppo in alcuni casi. Perché esporre una classe o un metodo a tutto nell'assembly?

Ho una pagina ASP.NET che eredita la mia pagina di base, che a sua volta eredita System.Web.UI.Page. In questa pagina, ho del codice che gestisce la segnalazione degli errori dell'utente finale per l'applicazione in un metodo protetto

ReportError("Uh Oh!");

Ora ho un controllo utente contenuto nella pagina. Voglio che il controllo utente sia in grado di chiamare i metodi di segnalazione errori nella pagina.

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

Non può farlo se il metodo ReportError è protetto. Posso renderlo interno, ma è esposto a qualsiasi codice nell'assembly. Lo voglio solo esposto agli elementi dell'interfaccia utente che fanno parte della pagina corrente (inclusi i controlli figlio). Più specificamente, desidero che la mia classe di controllo di base definisca esattamente gli stessi metodi di segnalazione degli errori e che chiami semplicemente i metodi nella pagina di base.

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

Credo che qualcosa come un amico possa essere utile e implementato nella lingua senza rendere la lingua meno "OO" come, forse come attributi, in modo che tu possa avere classi o metodi amici di classi o metodi specifici, consentendo allo sviluppatore di fornire molto accesso specifico. Forse qualcosa come ... (pseudo codice)

[Friend(B)]
class A {

    AMethod() { }

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

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

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

Nel caso del mio esempio precedente forse ho qualcosa di simile al seguente (si può sostenere la semantica, ma sto solo cercando di far passare l'idea):

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

A mio modo di vedere, il concetto di amico non ha più rischi che renderlo pubblico o creare metodi o proprietà pubblici per accedere ai membri. Semmai un amico consente un altro livello di granularità nell'accessibilità dei dati e consente di restringere tale accessibilità piuttosto che estenderla a livello interno o pubblico.


0

Se stai lavorando con C ++ e trovi te stesso usando la parola chiave friend, è un'indicazione molto forte, che hai un problema di progettazione, perché perché diamine una classe deve accedere ai membri privati ​​dell'altra classe ??


Sono d'accordo. Non ho mai bisogno della parola chiave amico. Viene generalmente utilizzato per risolvere complessi problemi di manutenzione.
Edouard A.

7
Operatore sovraccarico, chiunque ??
Siamo tutti Monica,

3
quante volte hai trovato sul serio un buon caso di sovraccarico dell'operatore?
bashmohandes,

8
Ti fornirò un caso molto semplice che ridimensiona completamente il tuo commento sul "problema di progettazione". Padre-figlio. Un genitore possiede i suoi figli. Un bambino nella collezione "Bambini" del genitore è di proprietà di quel genitore. Il setter della proprietà Parent su Child non dovrebbe essere impostabile da chiunque. Deve essere assegnato quando e solo quando il bambino viene aggiunto alla raccolta Bambini del genitore. Se è pubblico, chiunque può cambiarlo. Interno: chiunque in quell'assemblea. Privato? Nessuno. Rendere il setter un amico per genitore elimina quei casi e mantiene comunque le tue classi separate, ma strettamente correlate. Huzzah !!
Mark A. Donohoe,

2
Esistono molti casi di questo tipo, come la maggior parte del modello manager / gestito di base. C'è un'altra domanda su SO e nessuno ha immaginato un modo per farlo senza un amico. Non sono sicuro di cosa faccia pensare che una classe "basterà sempre". A volte più di una classe deve lavorare insieme.
Kaalus,

0

Bsd

È stato affermato che gli amici fanno male alla pura OOness. Che sono d'accordo.

È stato anche affermato che gli amici aiutano l'incapsulamento, che sono anche d'accordo.

Penso che l'amicizia dovrebbe essere aggiunta alla metodologia OO, ma non esattamente come in C ++. Vorrei avere alcuni campi / metodi a cui la mia classe di amici può accedere, ma NON vorrei che accedessero a TUTTI i miei campi / metodi. Come nella vita reale, permetterei ai miei amici di accedere al mio frigorifero personale ma non permetterei loro di accedere al mio conto bancario.

Uno può implementarlo come seguito

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

Ciò genererà naturalmente un avvertimento del compilatore e danneggerà l'intellisense. Ma farà il lavoro.

Da un lato, penso che un programmatore fiducioso dovrebbe fare l'unità di test senza accedere ai membri privati. questo è abbastanza fuori dal campo di applicazione, ma prova a leggere su TDD. tuttavia, se vuoi ancora farlo (avere amici come c ++) prova qualcosa del genere

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

quindi scrivi tutto il tuo codice senza definire UNIT_TESTING e quando vuoi fare il test unitario aggiungi #define UNIT_TESTING alla prima riga del file (e scrivi tutto il codice che fa il test unitario in #if UNIT_TESTING). Questo dovrebbe essere gestito con cura.

Dal momento che penso che il test unitario sia un cattivo esempio per l'uso degli amici, darei un esempio del perché penso che gli amici possano essere buoni. Supponiamo di avere un sistema di rottura (classe). Con l'uso, il sistema di rottura si consuma e deve essere rinnovato. Ora, vuoi che solo un meccanico autorizzato possa ripararlo. Per rendere l'esempio meno banale, direi che il meccanico avrebbe usato il suo cacciavite personale (privato) per sistemarlo. Ecco perché la classe meccanica dovrebbe essere amica della classe breakingSystem.


2
Quel parametro FriendClass non serve come controllo di accesso: puoi chiamare c.MyMethod((FriendClass) null, 5.5, 3)da qualsiasi classe.
Jesse McGrew,

0

L'amicizia può anche essere simulata usando "agenti" - alcune classi interiori. Prendi in considerazione il seguente esempio:

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

Potrebbe essere molto più semplice se utilizzato per l'accesso solo ai membri statici. I vantaggi di tale implementazione sono che tutti i tipi sono dichiarati nell'ambito interno delle classi di amicizia e, a differenza delle interfacce, consente l'accesso ai membri statici.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.