Pourquoi C # ne fournit-il pas le mot clé "friend" de style C ++? [fermé]


209

Le mot clé ami C ++ permet class Aà a de désigner class Bcomme son ami. Cela permet Class Bd'accéder aux private/ protectedmembres de class A.

Je n'ai jamais rien lu sur les raisons pour lesquelles cela a été laissé en dehors de C # (et VB.NET). La plupart des réponses à cette question précédente de StackOverflow semblent indiquer que c'est une partie utile de C ++ et qu'il y a de bonnes raisons de l'utiliser. D'après mon expérience, je devrais être d'accord.

Une autre question me semble être vraiment de savoir comment faire quelque chose de similaire frienddans une application C #. Bien que les réponses tournent généralement autour de classes imbriquées, cela ne semble pas aussi élégant que l'utilisation du friendmot - clé.

Le livre original de Design Patterns l' utilise régulièrement tout au long de ses exemples.

Donc, en résumé, pourquoi est friendabsent de C #, et quelle est la ou les meilleures pratiques pour le simuler en C #?

(À propos, le internalmot-clé n'est pas la même chose, il permet à toutes les classes de l'assemblage entier d'accéder aux internalmembres, tout en friendvous permet de donner à une certaine classe un accès complet à exactement une autre classe)


5
Je me sens protégé interne est un bon compromis ..
Gulzar Nazim

3
Ce n'est pas trop déroutant, n'est-ce pas? Comme je l'ai dit ci-dessus, le livre GOF l'utilise assez souvent dans des exemples. Pour moi, ce n'est pas plus déroutant qu'interne.
Ash

7
Je peux certainement voir des scénarios où ils peuvent être très utiles, comme déjà mentionné les tests unitaires. Mais voulez-vous vraiment que vos amis accèdent à vos privés?
danswain

2
Il est facile de répondre: C # propose "interne" comme modificateur d'accès qui accorde l'accès à tout le code dans le même module / assembly. Cela supprime le besoin de quelque chose comme un ami. En Java, le mot clé "protected" se comporte de la même manière pour l'accès à partir du même package.
sellibitze

2
@sellibitze, je mentionne les différences avec l'interne dans la question. Le problème avec Interanl est que toutes les classes de l'assemblage peuvent accéder aux membres internes. Cela rompt l'encapsulation car bon nombre de ces classes peuvent ne pas avoir besoin d'accès.
Ash

Réponses:


68

Avoir des amis dans la programmation est plus ou moins considéré comme «sale» et facile à abuser. Il rompt les relations entre les classes et sape certains attributs fondamentaux d'un langage OO.

Cela étant dit, c'est une fonctionnalité intéressante et je l'ai souvent utilisée moi-même en C ++; et aimerait l'utiliser également en C #. Mais je parie qu'à cause de l'OOness "pure" de C # (comparé au pseudo OOness de C ++) MS a décidé que parce que Java n'a pas de mot-clé ami, C # ne devrait pas non plus (je plaisante;))

Sur une note sérieuse: l'interne n'est pas aussi bon qu'un ami, mais il fait le travail. N'oubliez pas qu'il est rare que vous distribuiez votre code à des développeurs tiers sans passer par une DLL; donc tant que vous et votre équipe êtes au courant des classes internes et de leur utilisation, tout va bien.

EDIT Permettez-moi de clarifier comment le mot-clé ami sape la POO.

Les variables et méthodes privées et protégées sont peut-être l'une des parties les plus importantes de la POO. L'idée que les objets peuvent contenir des données ou une logique qu'ils seuls peuvent utiliser vous permet d'écrire votre implémentation de fonctionnalités indépendamment de votre environnement - et que votre environnement ne peut pas modifier les informations d'état qu'il n'est pas adapté à gérer. En utilisant friend, vous couplez les implémentations de deux classes ensemble - ce qui est bien pire si vous venez de coupler leur interface.


168
C # «interne» nuit en fait beaucoup plus à l'encapsulation que «ami» C ++. Avec "amitié", le propriétaire donne explicitement la permission à qui il veut. «Interne» est un concept ouvert.
Nemanja Trifunovic

21
Anders devrait être félicité pour les fonctionnalités qu'il a omises et non celles qu'il a incluses.
Mehrdad Afshari

21
Je ne suis pas d'accord que l'incapsulation interne de C # fait mal cependant. Le contraire à mon avis. Vous pouvez l'utiliser pour créer un écosystème encapsulé dans un assembly. Un certain nombre d'objets peuvent partager des types internes au sein de cet écosystème qui ne sont pas exposés au reste de l'application. J'utilise les astuces d'assemblage "ami" pour créer des assemblages de test unitaire pour ces objets.
Quibblesome

26
Cela n'a pas de sens. friendne permet pas aux classes ou fonctions arbitraires d'accéder aux membres privés. Il permet aux fonctions ou classes spécifiques marquées comme ami de le faire. La classe propriétaire du membre privé en contrôle toujours l'accès à 100%. Vous pourriez aussi bien argumenter que les méthodes des membres publics. Les deux garantissent que les méthodes spécifiquement répertoriées dans la classe ont accès aux membres privés de la classe.
jalf

8
Je pense tout le contraire. Si un assembly a 50 classes, si 3 de ces classes utilisent une classe utilitaire, alors aujourd'hui, votre seule option est de marquer cette classe utilitaire comme "interne". Ce faisant, vous le rendez non seulement disponible pour les 3 classes, mais également pour le reste des classes de l'assembly. Et si tout ce que vous vouliez faire était de fournir un accès plus fin de sorte que seules les trois classes en question aient accès à la classe utilitaire. Cela le rend en fait plus orienté objet car il améliore l'encapsulation.
zumalifeguard

105

Sur une note latérale. Utiliser un ami ne consiste pas à violer l'encapsulation, mais au contraire à l'appliquer. Comme les accesseurs + mutateurs, la surcharge des opérateurs, l'héritage public, le downcasting, etc. , il est souvent mal utilisé, mais cela ne signifie pas que le mot-clé n'a pas, ou pire, un mauvais but.

Voir le message de Konrad Rudolph dans l 'autre thread, ou si vous préférez voir l' entrée correspondante dans la FAQ C ++.


... et l'entrée correspondante dans la FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+1 pour " Utiliser un ami ne consiste pas à violer l'encapsulation, mais au contraire à l'appliquer "
Nawaz

3
Une question d'opinion sémantique je pense; et peut-être lié à la situation exacte en question. Faire d'une classe un friendlui donne accès à TOUS vos membres privés, même si vous n'avez besoin que de le laisser définir l'un d'entre eux. Il y a un argument fort à faire valoir que rendre des champs disponibles à une autre classe qui n'en a pas besoin compte comme "violer l'encapsulation". Il y a des endroits en C ++ où c'est nécessaire (surcharger les opérateurs), mais il y a un compromis d'encapsulation qui doit être soigneusement considéré. Il semble que les concepteurs C # aient estimé que le compromis n'en valait pas la peine.
GrandOpener

3
L'encapsulation consiste à appliquer des invariants. Quand on définit une classe comme amie d'une autre, on dit explicitement que les deux classes sont fortement liées en ce qui concerne la gestion des invariants de la première. Faire de la classe un ami est toujours mieux que d'exposer tous les attributs directement (en tant que champ public) ou via des setters.
Luc Hermitte

1
Exactement. Lorsque vous voulez utiliser un ami mais que vous ne pouvez pas, vous êtes obligé d'utiliser interne ou public, qui sont beaucoup plus susceptibles d'être poussés par des choses qui ne devraient pas. Surtout dans un environnement d'équipe.
Hatchling

50

Pour info, une autre chose liée mais pas tout à fait la même dans .NET est [InternalsVisibleTo], qui permet à un assembly de désigner un autre assembly (tel qu'un assembly de test unitaire) qui a (effectivement) un accès "interne" aux types / membres dans l'assemblage d'origine.


3
bon indice, car probablement souvent les personnes à la recherche d'un ami veulent trouver un moyen de faire des tests unitaires avec la possibilité d'avoir plus de «connaissances» internes.
SwissCoder

13

Vous devriez être en mesure d'accomplir le même genre de choses pour lesquelles «ami» est utilisé en C ++ en utilisant des interfaces en C #. Cela vous oblige à définir explicitement quels membres sont passés entre les deux classes, ce qui est un travail supplémentaire mais peut également rendre le code plus facile à comprendre.

Si quelqu'un a un exemple d'utilisation raisonnable de "ami" qui ne peut pas être simulé à l'aide d'interfaces, partagez-le! J'aimerais mieux comprendre les différences entre C ++ et C #.


Bon point. Avez-vous eu l'occasion de regarder la question SO précédente (posée par monoxyde et liée ci-dessus?. Je serais intéressé par la meilleure façon de résoudre cela en C #?
Ash

Je ne sais pas exactement comment le faire en C #, mais je pense que la réponse de Jon Skeet à la question du monoxyde va dans la bonne direction. C # autorise les classes imbriquées. Cependant, je n'ai pas vraiment essayé de coder et de compiler une solution. :)
Parappa

Je pense que le modèle du médiateur est un bon exemple de l'endroit où un ami serait gentil. Je refactore mes formulaires pour avoir un médiateur. Je veux que mon formulaire puisse appeler les méthodes "événement" comme dans le médiateur, mais je ne veux pas vraiment que d'autres classes appellent ces méthodes "événement". La fonctionnalité "Friend" donnerait au formulaire l'accès aux méthodes sans l'ouvrir à tout le reste de l'assembly.
Vaccano

3
Le problème avec l'approche d'interface consistant à remplacer «Friend» est qu'un objet expose une interface, ce qui signifie que n'importe qui peut convertir l'objet sur cette interface et avoir accès aux méthodes! Étendre l'interface à interne, protégé ou privé ne fonctionne pas non plus comme si cela fonctionnait, vous pouvez simplement définir la méthode en question! Pensez à une mère et à son enfant dans un centre commercial. Le public est tout le monde dans le monde. L'intérieur est juste les gens du centre commercial. Le privé est juste la mère ou l'enfant. «Ami» est quelque chose entre la mère et la fille uniquement.
Mark A. Donohoe

1
En voici un: stackoverflow.com/questions/5921612/... Comme je viens du fond C ++, le manque d'amis me rend fou presque quotidiennement. Au cours de mes quelques années avec C #, j'ai rendu publiques des centaines de méthodes où elles pourraient être privées et plus sûres, tout cela à cause du manque d'amis. Personnellement, je pense que l'ami est la chose la plus importante qui devrait être ajoutée à .NET
kaalus

13

Avec friendun concepteur C ++, il a un contrôle précis sur les personnes auxquelles les membres privés * sont exposés. Mais, il est obligé d'exposer chacun des membres privés.

Avec internal un concepteur C # a un contrôle précis sur l'ensemble des membres privés qu'il expose. De toute évidence, il ne peut exposer qu'un seul membre privé. Mais, il sera exposé à toutes les classes de l'assembly.

En règle générale, un concepteur souhaite exposer uniquement quelques méthodes privées à quelques autres classes sélectionnées. Par exemple, dans un modèle de fabrique de classes, il peut être souhaitable que la classe C1 soit instanciée uniquement par la fabrique de classes CF1. Par conséquent, la classe C1 peut avoir un constructeur protégé et une usine de classe ami CF1.

Comme vous pouvez le voir, nous avons 2 dimensions selon lesquelles l'encapsulation peut être violée. friendle brise le long d'une dimension, le internalfait le long de l'autre. Laquelle est la pire violation du concept d'encapsulation? Dur à dire. Mais ce serait bien d'avoir les deux friendet internaldisponibles. De plus, un bon ajout à ces deux serait le 3ème type de mot-clé, qui serait utilisé membre par membre (comme internal) et spécifie la classe cible (comme friend).

* Par souci de concision, j'utiliserai "privé" au lieu de "privé et / ou protégé".

- Pseudo


13

En fait, C # donne la possibilité d'obtenir le même comportement en pure POO sans mots spéciaux - ce sont des interfaces privées.

En ce qui concerne la question Quel est l'équivalent C # de friend? a été marqué comme duplicata de cet article et personne là-bas ne propose une très bonne réalisation - je vais montrer la réponse aux deux questions ici.

L'idée principale partait d'ici: qu'est-ce qu'une interface privée?

Disons que nous avons besoin d'une classe qui pourrait gérer les instances d'une autre classe et y appeler des méthodes spéciales. Nous ne voulons pas donner la possibilité d'appeler ces méthodes à d'autres classes. C'est exactement la même chose que le mot-clé friend c ++ fait dans le monde c ++.

Je pense qu'un bon exemple dans la pratique réelle pourrait être le modèle Full State Machine où un contrôleur met à jour l'objet d'état actuel et passe à un autre objet d'état si nécessaire.

Vous pourriez:

  • Le moyen le plus simple et le pire de rendre publique la méthode Update () - j'espère que tout le monde comprendra pourquoi c'est mauvais.
  • La prochaine façon est de le marquer comme interne. C'est assez bien si vous placez vos classes dans un autre assembly, mais même dans ce cas, chaque classe de cet assembly pourrait appeler chaque méthode interne.
  • Utilisez une interface privée / protégée - et j'ai suivi cette voie.

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

Eh bien, qu'en est-il de l'héritage?

Nous devons utiliser la technique décrite dans Puisque les implémentations de membres d'interface explicites ne peuvent pas être déclarées virtuelles et marquer IState comme protégé pour donner la possibilité de dériver également de 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()");
    }
}

Et enfin, exemple comment tester l'héritage de la classe Controller throw: ControllerTest.cs

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

J'espère que je couvre tous les cas et ma réponse a été utile.


C'est une réponse géniale qui nécessite plus de votes positifs.
KthProg

@max_cn C'est une très bonne solution, mais je ne vois pas un moyen de faire fonctionner cela avec des frameworks moqueurs pour les tests unitaires. Aucune suggestion?
Steve Land

Je suis un peu confus. Si je voulais qu'une classe externe puisse appeler Update dans votre premier exemple (interface privée), comment modifierais-je Controller pour permettre que cela se produise?
AnthonyMonterrosa le

@Steve Land, vous pouvez utiliser l'héritage comme dans ControllerTest.cs a été montré. Ajoutez toutes les méthodes publiques nécessaires dans une classe de test dérivée et vous aurez accès à tout le personnel protégé que vous avez dans la classe de base.
max_cn

@AnthonyMonterrosa, nous cachons Update de TOUTES les ressources externes, alors pourquoi voulez-vous le partager avec quelqu'un;)? Bien sûr, vous pouvez ajouter une méthode publique pour accéder aux membres privés, mais ce sera comme une porte dérobée pour toutes les autres classes. Quoi qu'il en soit, si vous en avez besoin à des fins de test, utilisez l'héritage pour créer quelque chose comme la classe ControllerTest pour y créer des cas de test.
max_cn

8

Vous pouvez vous rapprocher de C ++ "friend" avec le mot clé C # "internal" .


3
Proche, mais pas tout à fait là. Je suppose que cela dépend de la façon dont vous structurez vos assemblages / classes, mais il serait plus élégant de minimiser le nombre de classes auxquelles l'accès est donné en utilisant friend. Par exemple, dans un assembly utilitaire / d'assistance, toutes les classes ne doivent pas avoir accès aux membres internes.
Ash

3
@Ash: Vous devez vous y habituer, mais une fois que vous voyez les assemblages comme la pierre angulaire de vos applications, internalcela a beaucoup plus de sens et offre un excellent compromis entre l'encapsulation et l'utilité. L'accès par défaut de Java est cependant encore meilleur.
Konrad Rudolph

8

Friend est extrêmement utile lors de l'écriture d'un test unitaire.

Bien que cela entraîne une légère pollution de votre déclaration de classe, c'est aussi un rappel imposé par le compilateur de ce que les tests peuvent réellement se soucier de l'état interne de la classe.

Un idiome très utile et propre que j'ai trouvé est quand j'ai des classes d'usine, ce qui les rend amis des éléments qu'ils créent et qui ont un constructeur protégé. Plus précisément, c'était à l'époque où j'avais une seule usine chargée de créer des objets de rendu correspondants pour les objets de création de rapports, en effectuant un rendu dans un environnement donné. Dans ce cas, vous avez un seul point de connaissance sur la relation entre les classes de rédacteur de rapport (des éléments tels que des blocs d'image, des bandes de mise en page, des en-têtes de page, etc.) et leurs objets de rendu correspondants.


J'allais faire un commentaire sur "ami" étant utile pour les cours d'usine, mais je suis content de voir que quelqu'un l'a déjà fait.
Edward Robertson

8

C # manque le mot clé "friend" pour la même raison que sa destruction déterministe manquante. Changer les conventions permet aux gens de se sentir intelligents, comme si leurs nouvelles façons de faire étaient supérieures aux anciennes méthodes de quelqu'un d'autre. Tout est question de fierté.

Dire que «les classes d'amis sont mauvaises» est aussi à courte vue que d'autres déclarations non qualifiées comme «n'utilisez pas de gotos» ou «Linux est meilleur que Windows».

Le mot-clé "friend" combiné à une classe proxy est un excellent moyen d'exposer uniquement certaines parties d'une classe à d'autres classes spécifiques. Une classe proxy peut agir comme une barrière de confiance contre toutes les autres classes. "public" n'autorise aucun ciblage de ce type, et utiliser "protected" pour obtenir l'effet avec l'héritage est gênant s'il n'y a vraiment pas de relation conceptuelle "is a".


C # n'a pas de destruction déterministe car il est plus lent que le garbage collection. La destruction déterministe prend du temps pour chaque objet créé, tandis que le garbage collection ne prend du temps que pour les objets actuellement actifs. Dans la plupart des cas, cela est plus rapide, et plus le système contient d'objets de courte durée, plus la récupération de place est relativement rapide.
Edward Robertson le

1
@Edward Robertson La destruction déterministe ne prend pas de temps à moins qu'un destructeur ne soit défini. Mais dans ce cas, le garbage collection est bien pire, car vous devez alors utiliser un finaliseur, qui est plus lent et non déterministe.
kaalus

1
... et la mémoire n'est pas le seul type de ressource. Les gens agissent souvent comme si c'était vrai.
cubuspl42

7

Ce n'est en fait pas un problème avec C #. C'est une limitation fondamentale de l'IL. C # est limité par cela, comme tout autre langage .Net qui cherche à être vérifiable. Cette limitation inclut également les classes gérées définies dans C ++ / CLI ( Spec section 20.5 ).

Cela étant dit, je pense que Nelson a une bonne explication quant aux raisons pour lesquelles c'est une mauvaise chose.


7
-1. friendce n'est pas une mauvaise chose; au contraire, c'est bien mieux que internal. Et l'explication de Nelson est mauvaise et incorrecte.
Nawaz

4

Arrêtez de chercher des excuses pour cette limitation. ami est mauvais, mais interne est-il bon? c'est la même chose, seul cet ami vous donne un contrôle plus précis sur qui est autorisé à accéder et qui ne l'est pas.

C'est pour renforcer le paradigme d'encapsulation? vous devez donc écrire des méthodes d'accesseur et maintenant quoi? comment êtes-vous censé empêcher tout le monde (sauf les méthodes de la classe B) d'appeler ces méthodes? vous ne pouvez pas, parce que vous ne pouvez pas contrôler cela non plus, à cause d'un "ami" manquant.

Aucun langage de programmation n'est parfait. C # est l'un des meilleurs langages que j'ai vus, mais trouver des excuses idiotes pour des fonctionnalités manquantes n'aide personne. En C ++, je manque le système d'événement / délégué facile, la réflexion (+ dés / sérialisation automatique) et foreach, mais en C # je manque la surcharge d'opérateur (oui, continuez à me dire que vous n'en aviez pas besoin), les paramètres par défaut, un const qui ne peut être contourné, l'héritage multiple (ouais, continuez à me dire que vous n'en aviez pas besoin et que les interfaces étaient un remplacement suffisant) et la possibilité de décider de supprimer une instance de la mémoire (non, ce n'est pas horriblement mauvais sauf si vous êtes un bricoleur)


En C #, vous pouvez surcharger la plupart des opérateurs. Vous ne pouvez pas surcharger les opérateurs d'affectation, mais d'un autre côté vous pouvez remplacer ||/ &&tout en les gardant en court-circuit. Les paramètres par défaut ont été ajoutés en C # 4.
CodesInChaos

3

Je répondrai seulement à la question «Comment».

Il y a tellement de réponses ici, mais j'aimerais proposer une sorte de "modèle de conception" pour réaliser cette fonctionnalité. J'utiliserai un mécanisme de langage simple, qui comprend:

  • Interfaces
  • Classe imbriquée

Par exemple, nous avons 2 classes principales: étudiant et universitaire. L'étudiant a GPA auquel seule l'université est autorisée à accéder. Voici le 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();
}

2

Il y a l'InternalsVisibleToAttribute depuis .Net 3 mais je soupçonne qu'ils l'ont seulement ajouté pour répondre aux assemblages de test après la montée des tests unitaires. Je ne vois pas beaucoup d'autres raisons de l'utiliser.

Cela fonctionne au niveau de l'assemblage, mais il fait le travail là où l'interne ne fonctionne pas; c'est-à-dire où vous souhaitez distribuer un assembly, mais souhaitez qu'un autre assembly non distribué y ait un accès privilégié.

À juste titre, ils exigent que l'assemblage ami soit doté d'une clé forte pour éviter que quelqu'un ne crée un prétendu ami aux côtés de votre assemblage protégé.


2

J'ai lu de nombreux commentaires intelligents sur le mot-clé "ami" et je suis d'accord pour dire que c'est une chose utile, mais je pense que le mot-clé "interne" est moins utile, et ils sont tous les deux toujours mauvais pour la programmation OO pure.

Ce que nous avons? (en disant à propos de "ami", je dis aussi à propos de "interne")

  • est-ce que l'utilisation de "friend" rend le code moins pur en ce qui concerne oo?
  • Oui;

  • le fait de ne pas utiliser "ami" améliore-t-il le code?

  • non, nous devons encore établir des relations privées entre les classes, et nous ne pouvons le faire que si nous cassons notre belle encapsulation, donc ce n'est pas non plus bon, je peux dire ce que c'est encore plus mal que d'utiliser "ami".

Utiliser friend pose des problèmes locaux, ne pas l'utiliser crée des problèmes pour les utilisateurs de la bibliothèque de code.

la bonne solution commune pour le langage de programmation que je vois comme ceci:

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

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

Qu'est-ce que tu en penses? Je pense que c'est la solution orientée objet la plus courante et la plus pure. Vous pouvez ouvrir l'accès de n'importe quelle méthode de votre choix à n'importe quelle classe de votre choix.


Je ne suis pas une sentinelle de la POO, mais il semble que cela résout les plaintes de tout le monde contre les amis et les internes. Mais je suggérerais d'utiliser un attribut pour éviter l'extension de la syntaxe de C #: [PublicFor Bar] void addBar (Bar bar) {} ... Pas que je sois sûr que cela ait du sens; Je ne sais pas comment les attributs fonctionnent ou s'ils peuvent jouer avec l'accessibilité (ils font beaucoup d'autres choses «magiques»).
Grault

1

Je soupçonne que cela a quelque chose à voir avec le modèle de compilation C # - construire IL le JIT en le compilant au moment de l'exécution. ie: la même raison pour laquelle les génériques C # sont fondamentalement différents des génériques C ++.


Je pense que le compilateur pourrait le supporter assez facilement au moins entre les classes d'un assembly. Juste une question de détente de la vérification d'accès pour la classe d'amis sur la classe cible. L'ami entre les classes dans les assemblys externes peut nécessiter des modifications plus fondamentales du CLR.
Ash

1

vous pouvez le garder privé et utiliser la réflexion pour appeler des fonctions. Le framework de test peut le faire si vous lui demandez de tester une fonction privée


Sauf si vous travaillez sur une application Silverlight.
kaalus

1

J'utilisais régulièrement Friend, et je ne pense pas que ce soit une violation de la POO ou un signe de défaut de conception. Il y a plusieurs endroits où c'est le moyen le plus efficace d'arriver à la bonne fin avec le moins de code.

Un exemple concret est celui de la création d'assemblys d'interface qui fournissent une interface de communication à d'autres logiciels. Généralement, il existe quelques classes lourdes qui gèrent la complexité du protocole et des particularités des pairs, et fournissent un modèle de connexion / lecture / écriture / transfert / déconnexion relativement simple impliquant le passage de messages et de notifications entre l'application cliente et l'assembly. Ces messages / notifications doivent être enveloppés dans des classes. Les attributs doivent généralement être manipulés par le logiciel de protocole car il est leur créateur, mais beaucoup de choses doivent rester en lecture seule pour le monde extérieur.

Il est tout simplement ridicule de déclarer que c'est une violation de la POO pour que la classe protocol / "creator" ait un accès intime à toutes les classes créées - la classe creator a dû briser chaque bit de données en montant. Ce que j'ai trouvé le plus important est de minimiser toutes les lignes de code supplémentaires BS auxquelles le modèle "OOP for OOP's Sake" conduit habituellement. Les spaghettis supplémentaires font juste plus d'insectes.

Les gens savent-ils que vous pouvez appliquer le mot-clé interne au niveau de l'attribut, de la propriété et de la méthode? Ce n'est pas seulement pour la déclaration de classe de niveau supérieur (bien que la plupart des exemples semblent le montrer.)

Si vous avez une classe C ++ qui utilise le mot-clé friend et que vous souhaitez émuler cela dans une classe C #: 1. déclarez la classe C # public 2. déclarez tous les attributs / propriétés / méthodes qui sont protégés en C ++ et donc accessibles aux amis comme internal en C # 3. créer des propriétés en lecture seule pour l'accès public à tous les attributs et propriétés internes

Je suis d'accord que ce n'est pas à 100% la même chose qu'un ami, et le test unitaire est un exemple très précieux du besoin de quelque chose comme ami (tout comme le code de journalisation de l'analyseur de protocole). Cependant, internal fournit l'exposition aux classes que vous souhaitez exposer, et [InternalVisibleTo ()] gère le reste - il semble qu'il soit né spécifiquement pour le test unitaire.

En ce qui concerne l'ami "étant meilleur parce que vous pouvez contrôler explicitement quelles classes ont accès" - qu'est-ce que diable font un tas de classes perverses suspectes dans le même assemblage en premier lieu? Partitionnez vos assemblages!


1

L'amitié peut être simulée en séparant les interfaces et les implémentations. L'idée est: " Exiger une instance concrète mais restreindre l'accès de construction de cette instance ".

Par exemple

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

En dépit du fait qu'elle ItsMeYourFriend()est publique, seule la Friendclasse peut y accéder, car personne d'autre ne peut obtenir une instance concrète de la Friendclasse. Elle a un constructeur privé, tandis que la New()méthode factory renvoie une interface.

Voir mon article Amis et membres de l'interface interne sans frais avec le codage des interfaces pour plus de détails.


Malheureusement, l'amitié ne peut être simulée d'aucune manière. Voir cette question: stackoverflow.com/questions/5921612/…
kaalus

1

Certains ont suggéré que les choses peuvent devenir incontrôlables en utilisant un ami. Je serais d'accord, mais cela n'en diminue pas l'utilité. Je ne suis pas certain que cet ami blesse nécessairement le paradigme OO pas plus que de rendre publics tous les membres de votre classe. Certes, le langage vous permettra de rendre tous vos membres publics, mais c'est un programmeur discipliné qui évite ce type de modèle de conception. De même, un programmeur discipliné réserverait l'utilisation d'un ami pour des cas spécifiques où cela a du sens. Je sens que l'intérieur expose trop dans certains cas. Pourquoi exposer une classe ou une méthode à tout dans l'assembly?

J'ai une page ASP.NET qui hérite de ma propre page de base, qui à son tour hérite de System.Web.UI.Page. Dans cette page, j'ai du code qui gère les rapports d'erreur de l'utilisateur final pour l'application dans une méthode protégée

ReportError("Uh Oh!");

Maintenant, j'ai un contrôle utilisateur qui est contenu dans la page. Je veux que le contrôle utilisateur puisse appeler les méthodes de rapport d'erreur dans la page.

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

Il ne peut pas faire cela si la méthode ReportError est protégée. Je peux le rendre interne, mais il est exposé à n'importe quel code de l'assembly. Je veux juste qu'il soit exposé aux éléments de l'interface utilisateur qui font partie de la page actuelle (y compris les contrôles enfants). Plus précisément, je veux que ma classe de contrôle de base définisse exactement les mêmes méthodes de rapport d'erreurs et appelle simplement des méthodes dans la page de base.

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

Je pense que quelque chose comme friend pourrait être utile et implémenté dans le langage sans rendre le langage moins "OO" comme, peut-être en tant qu'attributs, afin que vous puissiez avoir des classes ou des méthodes amis de classes ou de méthodes spécifiques, permettant au développeur de fournir très accès spécifique. Peut-être quelque chose comme ... (pseudo code)

[Friend(B)]
class A {

    AMethod() { }

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

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

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

Dans le cas de mon exemple précédent, j'ai peut-être quelque chose comme ce qui suit (on peut discuter de la sémantique, mais j'essaie juste de faire passer l'idée):

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

À mon avis, le concept d'ami n'a pas plus de risque que de rendre les choses publiques ou de créer des méthodes ou des propriétés publiques pour accéder aux membres. Si quelque chose d'ami permet un autre niveau de granularité dans l'accessibilité des données et vous permet de restreindre cette accessibilité plutôt que de l'élargir avec interne ou public.


0

Si vous travaillez avec C ++ et que vous vous trouvez en utilisant le mot-clé friend, c'est une indication très forte, que vous avez un problème de conception, car pourquoi diable une classe a besoin d'accéder aux membres privés d'une autre classe ??


Je suis d'accord. Je n'ai jamais besoin du mot-clé ami. Il est généralement utilisé pour résoudre des problèmes de maintenance complexes.
Edouard A.

7
Surcharge de l'opérateur, n'importe qui ??
We Are All Monica

3
combien de fois avez-vous trouvé un bon argument pour une surcharge de l'opérateur sérieusement?
bashmohandes

8
Je vais vous donner un cas très simple qui démystifie complètement votre commentaire de «problème de conception». Parent-enfant. Un parent possède ses enfants. Un enfant de la collection «Children» du parent appartient à ce parent. Le setter de la propriété Parent sur Child ne doit pas être défini par n'importe qui. Il doit être attribué lorsque et uniquement lorsque l'enfant est ajouté à la collection Children du parent. tS'il est public, n'importe qui peut le changer. Interne: n'importe qui dans cette assemblée. Privé? Personne. Faire du passeur un ami pour le parent élimine ces cas et maintient toujours vos classes séparées, mais étroitement liées. Huzzah !!
Mark A. Donohoe

2
Il existe de nombreux cas de ce type, comme la plupart des modèles de gestion / gestion de base. Il y a une autre question sur SO et personne n'a trouvé un moyen de le faire sans ami. Je ne sais pas ce qui fait penser qu'une classe "suffira toujours". Parfois, plus d'une classe doit travailler ensemble.
kaalus

0

Bsd

Il a été déclaré que, les amis blessent la pure OOness. Ce que je suis d'accord.

Il a également été déclaré que les amis aident à l'encapsulation, ce que je suis également d'accord.

Je pense que l'amitié devrait être ajoutée à la méthodologie OO, mais pas tout à fait comme elle en C ++. J'aimerais avoir certains champs / méthodes auxquels ma classe d'amis peut accéder, mais je ne voudrais PAS qu'ils accèdent à TOUS mes champs / méthodes. Comme dans la vraie vie, je laisserais mes amis accéder à mon réfrigérateur personnel mais je ne les laisserais pas accéder à mon compte bancaire.

On peut mettre en œuvre cela comme suit

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

Cela générera bien sûr un avertissement du compilateur et endommagera l'intellisense. Mais cela fera le travail.

Sur une note latérale, je pense qu'un programmeur confiant devrait faire l'unité de test sans accéder aux membres privés. c'est tout à fait hors de portée, mais essayez de lire sur TDD. cependant, si vous voulez toujours le faire (avoir C ++ comme des amis), essayez quelque chose comme

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

donc vous écrivez tout votre code sans définir UNIT_TESTING et lorsque vous voulez faire le test unitaire vous ajoutez #define UNIT_TESTING à la première ligne du fichier (et écrivez tout le code qui fait le test unitaire sous #if UNIT_TESTING). Cela devrait être manipulé avec soin.

Puisque je pense que les tests unitaires sont un mauvais exemple pour l'utilisation d'amis, je donnerais un exemple pourquoi je pense que les amis peuvent être bons. Supposons que vous ayez un système de rupture (classe). Avec l'utilisation, le système de freinage s'use et doit être rénové. Maintenant, vous voulez que seul un mécanicien agréé le répare. Pour rendre l'exemple moins trivial, je dirais que le mécanicien utiliserait son tournevis personnel (privé) pour le réparer. C'est pourquoi la classe de mécanicien devrait être l'amie de la classe BreakSystem.


2
Ce paramètre FriendClass ne sert pas de contrôle d'accès: vous pouvez appeler c.MyMethod((FriendClass) null, 5.5, 3)depuis n'importe quelle classe.
Jesse McGrew

0

L'amitié peut également être simulée en utilisant des «agents» - certaines classes internes. Prenons l'exemple suivant:

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

Cela pourrait être beaucoup plus simple lorsqu'il est utilisé pour accéder uniquement aux membres statiques. Les avantages d'une telle implémentation sont que tous les types sont déclarés dans la portée interne des classes d'amitié et, contrairement aux interfaces, cela permet d'accéder aux membres statiques.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.