Disons que j'ai besoin d'une méthode d'assistance privée simple, et intuitivement dans le code, cela aurait du sens comme méthode d'extension. Existe-t-il un moyen d'encapsuler cet assistant à la seule classe qui a réellement besoin de l'utiliser?
Par exemple, j'essaye ceci:
class Program
{
static void Main(string[] args)
{
var value = 0;
value = value.GetNext(); // Compiler error
}
static int GetNext(this int i)
{
return i + 1;
}
}
Le compilateur ne "voit" pas la méthode d'extension GetNext()
. L'erreur est:
La méthode d'extension doit être définie dans une classe statique non générique
Assez juste, donc je l'enveloppe dans sa propre classe, mais toujours encapsulé dans l'objet auquel il appartient:
class Program
{
static void Main(string[] args)
{
var value = 0;
value = value.GetNext(); // Compiler error
}
static class Extensions
{
static int GetNext(this int i)
{
return i + 1;
}
}
}
Toujours pas de dés. Maintenant, l'erreur indique:
La méthode d'extension doit être définie dans une classe statique de niveau supérieur; Les extensions sont une classe imbriquée.
Y a-t-il une raison impérieuse pour cette exigence? Il y a des cas où une méthode d'assistance devrait vraiment être encapsulée en privé, et il y a des cas où le code est beaucoup plus propre et plus lisible/supportable si une méthode d'aide est une méthode d'extension. Pour les cas où ces deux points se croisent, peut-on tous deux être satisfaits ou devons-nous choisir l'un plutôt que l'autre?
Je crois que le meilleur que vous puissiez obtenir en général est internal static
classe avec internal static
méthodes d'extension. Comme il se trouvera dans votre propre assembly, les seules personnes dont vous avez besoin pour empêcher l'utilisation de l'extension sont les auteurs de l'assembly - donc certains espaces de noms nommés explicitement (comme My.Extensions.ForFoobarOnly
) peut suffire pour faire allusion pour éviter une mauvaise utilisation.
La restriction minimale internal
couverte dans implémenter l'extension article
La classe doit être visible par le code client ... méthode avec au moins la même visibilité que la classe conteneur.
Remarque: je rendrais l'extension publique de toute façon pour simplifier les tests unitaires, mais je mettrais un espace de noms explicitement nommé comme Xxxx.Yyyy.Internal
pour que les autres utilisateurs de l'assembly ne s'attendent pas à ce que les méthodes soient prises en charge/appelables. S'appuyer essentiellement sur une convention autre que la compilation du temps d'exécution.
Y a-t-il une raison impérieuse pour cette exigence?
Ce n'est pas la bonne question à poser. La question posée par l'équipe de conception du langage lors de la conception de cette fonctionnalité était:
Existe-t-il une raison impérieuse pour permettre aux méthodes d'extension d'être déclarées dans des types statiques imbriqués?
Étant donné que les méthodes d'extension ont été conçues pour faire fonctionner LINQ et que LINQ n'a pas de scénarios où les méthodes d'extension seraient privées à un type, la réponse était "non, il n'y a pas une telle raison impérieuse".
En éliminant la possibilité de placer des méthodes d'extension dans des types imbriqués statiques, aucune des règles de recherche de méthodes d'extension dans des types imbriqués statiques n'a dû être pensée, argumentée, conçue, spécifiée, implémentée, testée, documentée, expédiée aux clients, ou rendu compatible avec toutes les futures fonctionnalités de C #. C'était une économie de coûts importante.
Ce code compile et fonctionne:
static class Program
{
static void Main(string[] args)
{
var value = 0;
value = value.GetNext(); // Compiler error
}
static int GetNext(this int i)
{
return i + 1;
}
}
Faire attention à static class Program
ligne qui était nécessaire pour le compilateur.
Je crois que c'est la façon dont ils ont implémenté la compilation des méthodes d'extensions.
En regardant l'IL, il semble qu'ils ajoutent des attributs supplémentaires à la méthode.
.method public hidebysig static int32 GetNext(int32 i) cil managed
{
.custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
.maxstack 2
.locals init (
[0] int32 num)
L_0000: nop
L_0001: ldarg.0
L_0002: ldc.i4.1
L_0003: add
L_0004: dup
L_0005: starg.s i
L_0007: stloc.0
L_0008: br.s L_000a
L_000a: ldloc.0
L_000b: ret
}
Il y a probablement des éléments très fondamentaux qui nous manquent et qui ne fonctionnent tout simplement pas, c'est pourquoi la restriction est en place. Cela pourrait aussi être simplement qu'ils voulaient forcer les pratiques de codage. Malheureusement, cela ne fonctionne tout simplement pas et doit être dans des classes statiques de niveau supérieur.