Pourquoi utiliser une méthode publique dans une classe interne?

250

Il y a beaucoup de code dans l'un de nos projets qui ressemble à ceci:

internal static class Extensions
{
    public static string AddFoo(this string s)
    {
        if (s == null)
        {
            return "Foo";
        }

        return $({s}Foo);
    }
}

Y a-t-il une raison explicite de le faire autre que "il est plus facile de rendre le type public plus tard?"

Je soupçonne que cela n'a d'importance que dans des cas très étranges (réflexion dans Silverlight) ou pas du tout.

bopapa_1979
la source
1
D'après mon expérience, c'est la pratique normale.
phoog
2
@phoog, OK. mais pourquoi est-ce la pratique normale? Peut-être juste plus facile que de changer un tas de méthodes en interne? Solution rapide -> marquer le type interne à la place?
bopapa_1979
c'est ce que j'ai toujours supposé. Je n'ai cependant pas d'analyse bien pensée, c'est pourquoi je n'ai pas posté de réponse.
phoog
2
La réponse d'Eric Lippert résume très bien ma pensée et évoque également quelque chose qui se cachait dans mon cerveau en essayant de trouver une issue: les implémentations implicites des membres de l'interface doivent être publiques.
phoog
Cette méthode n'est-elle pas égale à return s + "Foo";? L' +opérateur ne se soucie pas des chaînes nulles ou vides.
Mikkel R. Lund

Réponses:

419

MISE À JOUR: Cette question a fait l'objet de mon blog en septembre 2014 . Merci pour la grande question!

Il y a un débat considérable sur cette question, même au sein de l'équipe de compilation elle-même.

Tout d'abord, il est sage de comprendre les règles. Un membre public d'une classe ou d'une structure est un membre accessible à tout ce qui peut accéder au type conteneur . Ainsi, un membre public d'une classe interne est effectivement interne.

Alors maintenant, étant donné une classe interne, ses membres auxquels vous souhaitez accéder dans l'assembly doivent-ils être marqués comme publics ou internes?

Mon opinion est la suivante: marquer ces membres comme publics.

J'utilise "public" pour signifier "ce membre n'est pas un détail d'implémentation". Un membre protégé est un détail d'implémentation; il y aura quelque chose qui sera nécessaire pour faire fonctionner une classe dérivée. Un membre interne est un détail d'implémentation; quelque chose d'autre interne à cet assembly a besoin du membre pour fonctionner correctement. Un membre public dit que "ce membre représente la fonctionnalité clé et documentée fournie par cet objet".

Fondamentalement, mon attitude est la suivante: supposons que j'ai décidé de faire de cette classe interne une classe publique. Pour ce faire, je veux changer exactement une chose : l'accessibilité de la classe. Si transformer une classe interne en classe publique signifie que je dois également transformer un membre interne en membre public, alors ce membre faisait partie de la surface publique de la classe, et il aurait dû être public en premier lieu.

D'autres personnes sont en désaccord. Il y a un contingent qui dit qu'ils veulent pouvoir regarder la déclaration d'un membre et savoir immédiatement si elle va être appelée uniquement à partir du code interne.

Malheureusement, cela ne fonctionne pas toujours bien; par exemple, une classe interne qui implémente une interface interne doit toujours avoir les membres d'implémentation marqués comme publics, car ils font partie de la surface publique de la classe .

Eric Lippert
la source
11
J'aime cette réponse ... elle correspond bien à mon attitude envers l'écriture de code qui se documente le plus possible, et la portée est un excellent moyen de diffuser l'intention, surtout si les autres membres de votre équipe comprennent votre convention.
bopapa_1979
10
+1 pour avoir dit ce que je voulais dire, mais pour le formuler beaucoup mieux que je ne pouvais (également pour avoir soulevé l'angle d'interface, bien que je note que ce n'est vrai que pour les implémentations implicites des membres).
phoog
2
La partie Interface est importante - il est impossible d'implémenter une méthode d'interface en utilisant interne, c'est une déclaration d'interface publique ou explicite. Le mot public est donc surchargé de deux significations.
Michael Stum
8
D'un point de vue généalogique, votre argument en faveur de publicest très convaincant. Cependant, je trouve que "ça ne marche pas toujours bien ..." est particulièrement puissant. La perte de cohérence dévalue tous les avantages «en un coup d'œil». Utiliser internalde cette manière signifie construire délibérément des intuitions qui sont parfois fausses. Avoir une intuition presque correcte est l'OMI une chose horrible à avoir pour la programmation.
Brian
3
Citation importante de votre article de blog: "Mon conseil est de discuter du problème avec votre équipe, de prendre une décision, puis de vous y tenir."
bopapa_1979
17

Si la classe l'est internal, peu importe du point de vue de l'accessibilité que vous marquiez une méthode internalou public. Cependant, il est toujours bon d'utiliser le type que vous utiliseriez si la classe l'était public.

Alors que certains ont dit que cela facilite les transitions de internalà public. Il fait également partie de la description de la méthode. Internalles méthodes sont généralement considérées comme dangereuses pour un accès sans entrave, tandis que les publicméthodes sont considérées comme (principalement) des jeux gratuits.

En utilisant internalou publiccomme vous le feriez dans une publicclasse, vous vous assurez que vous communiquez le style d'accès attendu, tout en facilitant le travail requis pour créer la classe publicà l'avenir.

Guvante
la source
J'ai tendance à tout limiter dans la mesure du possible tout en créant une bonne API et en permettant à mon consommateur potentiel de faire ce que je veux qu'il fasse. Je suis également avec vous pour marquer le membre comme je le ferais si le cours était public. Plus précisément, je ne marquerais un membre public que si je le considère toujours sûr. Sinon, quelqu'un d'autre marquant la classe publique plus tard (cela arrive) pourrait exposer des membres non sûrs.
bopapa_1979
@Eric: Si vous voulez renoncer à déterminer la sécurité d'une méthode jusqu'à ce que vous soyez prêt, c'est bien, mais je faisais référence uniquement aux méthodes jugées sûres, auquel cas il n'y a pas d'exposition.
Guvante
10

Je marque souvent mes méthodes dans des classes internes publiques au lieu de internes car a) cela n'a pas vraiment d'importance et b) j'utilise interne pour indiquer que la méthode est délibérément interne (il y a une raison pour laquelle je ne veux pas exposer cela Par conséquent, si j'ai une méthode interne, je dois vraiment comprendre la raison pour laquelle elle est interne avant de la changer en public alors que si je traite avec une méthode publique dans une classe interne, je dois vraiment réfléchir à la raison pour laquelle la méthode la classe est interne par opposition à la raison pour laquelle chaque méthode est interne.

Ondiamond ǤeezeƦ
la source
Merci d'avoir répondu. J'ai tendance à insister obstinément sur le fait que «tout» est important lors du codage :)
bopapa_1979
1
Tu as raison Eric, c'était un peu un commentaire à jeter. J'espère que le reste de ma réponse m'a été utile. Je pense que la réponse d'Eric Lippert est ce que j'essayais d'exprimer mais il l'a expliqué tellement mieux.
Ɖiamond ǤeezeƦ
8

Je soupçonne "qu'il est plus facile de rendre le type public plus tard?" est-ce.

Les règles de portée signifient que la méthode ne sera visible qu'en internaltant que - donc peu importe que les méthodes soient marquées publicou internal.

Une possibilité qui vient à l'esprit est que la classe était publique et a ensuite été remplacée par internalet que le développeur n'a pas pris la peine de changer tous les modificateurs d'accessibilité de la méthode.

Oded
la source
1
+1 pour avoir cloué le scénario évident "la classe publique est devenue interne".
bopapa_1979
8

Dans certains cas, il se peut également que le type interne implémente une interface publique, ce qui signifierait que toutes les méthodes définies sur cette interface devront toujours être déclarées publiques.

Trevor Pilley
la source
2

C'est la même chose, la méthode publique sera vraiment marquée comme interne car elle est à l'intérieur d'une classe interne, mais elle a un avantage (comme vous l'avez invité), si vous voulez marquer la classe comme publique, vous devez changer moins de code.

Mario Corchero
la source
2
il y a déjà une question connexe: stackoverflow.com/questions/711361/…
Mario Corchero
0

internalindique que le membre n'est accessible que depuis le même assemblage. D' autres classes dans cette assemblée peuvent accéder au internal publicmembre, mais ne seraient pas en mesure d'un accès privateou protectedmembre, internalou non.

Ryan P
la source
Mais si les méthodes étaient marquées internalau lieu de public, leur visibilité ne changerait pas. Je pense que c'est ce que le PO demande.
Oded
1
@zapthedingbat - le message est correct, mais répond-il réellement à la question?
Oded
1
Bon, la question est POURQUOI les marquer de cette façon. Je comprends les bases de la portée. Merci pour cette tentative!
bopapa_1979
0

J'ai en fait eu du mal avec ça aujourd'hui. Jusqu'à présent, j'aurais dit que les méthodes devraient toutes être marquées avec internalsi la classe était internalet aurait considéré autre chose que du mauvais codage ou de la paresse, spécialement dans le développement d'entreprise; cependant, j'ai dû sous-classer une publicclasse et remplacer l'une de ses méthodes:

internal class SslStreamEx : System.Net.Security.SslStream
{
    public override void Close()
    {
        try
        {
            // Send close_notify manually
        }
        finally
        {
            base.Close();
        }
    }
}

La méthode DOIT être publicet cela m'a fait penser qu'il n'y a vraiment aucun intérêt logique à définir des méthodes comme à internalmoins qu'elles ne le soient vraiment, comme l'a dit Eric Lippert.

Jusqu'à présent, je ne me suis jamais vraiment arrêté pour y penser, je l'ai juste accepté, mais après avoir lu le post d'Eric, cela m'a vraiment fait réfléchir et après beaucoup de délibérations, cela a beaucoup de sens.

Tempête
la source
0

Il y a une différence. Dans notre projet, nous avons créé de nombreuses classes internes, mais nous effectuons des tests unitaires dans un autre assembly et dans nos informations d'assembly, nous avons utilisé InternalsVisibleTo pour permettre à l'assembly UnitTest d'appeler les classes internes. J'ai remarqué que si la classe interne a un constructeur interne, nous ne pouvons pas créer d'instance à l'aide d'Activator.CreateInstance dans l'assembly de test unitaire pour une raison quelconque. Mais si nous changeons le constructeur en public mais que la classe est toujours interne, cela fonctionne très bien. Mais je suppose que c'est un cas très rare (comme Eric l'a dit dans le post original: Reflection).

Farrah Jiang
la source
0

Je pense avoir une opinion supplémentaire à ce sujet. Au début, je me demandais comment il était logique de déclarer quelque chose au public dans une classe interne. Ensuite, je me suis retrouvé ici, lisant que cela pourrait être bien si vous décidez plus tard de changer la classe en public. Vrai. Donc, un motif s'est formé dans mon esprit: si cela ne change pas le comportement actuel, alors soyez permissif et autorisez des choses qui n'ont pas de sens (et qui ne blessent pas) dans l'état actuel du code, mais plus tard, si vous changer la déclaration de la classe.

Comme ça:

public sealed class MyCurrentlySealedClass
{
    protected void MyCurretlyPrivateMethod()
    {
    }
}

Selon le "modèle" que j'ai mentionné ci-dessus, cela devrait être parfaitement bien. Il suit la même idée. Il se comporte comme une privateméthode, car vous ne pouvez pas hériter de la classe. Mais si vous supprimez la sealedcontrainte, elle est toujours valide: les classes héritées peuvent voir cette méthode, ce que je voulais absolument atteindre. Mais vous obtenez un avertissement:, CS0628ou CA1047. Les deux visent à ne pas déclarer de protectedmembres dans une sealedclasse. De plus, j'ai trouvé un accord total sur le fait que cela n'a pas de sens: avertissement «membre protégé dans une classe scellée» (une classe singleton)

Donc, après cet avertissement et la discussion liée, j'ai décidé de tout rendre interne ou moins, dans une classe interne, car cela correspond plus à ce genre de pensée, et nous ne mélangeons pas différents "modèles".

Hix
la source
Je préfère radicalement le faire comme (ou du moins pour les raisons), a déclaré Eric Lippert. Son article vaut vraiment la peine d'être lu.
bopapa_1979