Injection de dépendances en C# .NET

est un peu alambiqué et compliqué.
Heureusement, l'implémentation de l'injection de dépendances par C# .NET est assez simple. À mon avis, c'est beaucoup plus simple que l'implémentation fournie par Spring Framework de Java. Si vous comprenez les bases du concept d'injection de dépendances mais que vous ne l'avez pas encore essayé en pratique, C# .NET pourrait être votre meilleur pari.
J'ai écrit peut être utile.
En général, que l'injection de dépendances soit en jeu ou non, les classes peuvent spécifier des types appelésdépendances– qu'ils onta unrelations avec.
Dans l'injection de dépendances, les instances de classes ne sont pas responsables de la création d'instances de leurs dépendances. Au lieu de cela, un conteneur de gestion maintienta un relations avec les instances des classes, et l'utilisateur spécifie au conteneur quelles implémentations des dépendances il souhaite utiliser en appelant l'une des méthodes du conteneur, ou en écrivant ce qu'on appelle un "code de configuration" qui est interprété par le conteneur. Lors de l'exécution, le conteneur "injecte" ces implémentations dans les instances de classe.
Pourquoi utiliser l'injection de dépendance ? L'essentiel est deinterface séparée de l'implémentation . Pourquoi est-ce important? Je vous suggère de lire l'article lié ci-dessus pour plus de détails.
La première chose à savoir lors de l'apprentissage de l'injection de dépendances dans C# .NET est que Microsoft utilise une terminologie alternative lors de l'examen des concepts d'injection de dépendances. Si vous souhaitez comprendre la documentation Microsoft, vous devez connaître cette terminologie. Alors, voici un peu de vocabulaire :
Microsoft phrase | Signification |
---|---|
service | dépendance |
enregistrement des services | le stockage des dépendances dans le conteneur de gestion |
service de résolution | l'injection à l'exécution d'une dépendance |
leServiceDescriptor
la classe est ce qui représente un service (rappelons que « service » signifie « dépendance »). Le constructeur le plus terre-à-terre deServiceDescriptor
est comme suit:
Publique ServiceDescriptor(Taper type de service,Taper type d'implémentation,ServiceLifetime durée de vie)
Ainsi, nous voyons qu'en C # .NET, un service englobe essentiellement le type de la dépendance, le type de l'implémentation préférée pour ladite dépendance et la "durée de vie" de la dépendance.
À mon avis, la "durée de vie" devrait vraiment s'appeler "multiplicité d'instanciation", puisque la valeur dedurée de vie
dans le constructeur ci-dessus détermine si le conteneur de gestion doit ou non créer plusieurs instances de la dépendance et, si tel est le cas, comment procéder.
Spécifiquement,ServiceLifetime
est unénumération
qui peut prendre la valeurSingleton
,Transitoire
ouPortée
.
Singleton
indique que le conteneur de gestion (que nous n'avons pas encore vu) garantira qu'une seule instance du service sera créée pendant toute la durée de vie du programme. Toutes les instances de classe qui dépendent du service partageront le même service.Transitoire
indique que le conteneur de gestion s'assurera qu'une nouvelle instance de la dépendance sera créée chaque fois qu'une instance de classe différente en aura besoin.Le sens de
Portée
est un peu compliqué pour une première passe d'injection de dépendances en C# .NET. Si vous voulez en savoir plus, lisez.
ServiceDescriptor
Propriétés
Vous avez déjà vu leServiceDescriptor
constructeur, qui est ce qui est le plus important en ce qui concerne la compréhensionServiceDescriptor
. Pour un peu plus de détails, voici les propriétés publiques qui sont enveloppées par leServiceDescriptor
:
Publique FonctionIServiceProvider,objet> ? ImplementationFactory{avoir ; }
Publique objet? Instance de mise en œuvre{avoir ; }
Publique Taper? Type de mise en œuvre{avoir ; }
Publique ServiceLifetime Durée de vie{avoir ; }
Publique Taper Type de service{avoir ; }
Certains des éléments ci-dessus peuvent prêter à confusion, alors voici quelques notes de clarification :
Fonction
représente une fonction qui prend un argument de typeT1
en entrée et renvoie un typeT2
instance en sortie. Ainsi, leImplementationFactory
propriété est une fonction qui prend uneIServiceProvider
en entrée et renvoie une instance de l'implémentation en sortie.ImplementationFactory
peuvent être considérées comme des instructions d'emballage sur la façon de créer une instance de l'instance d'implémentation.Pour tout type
J
l'expressionT ?
est un raccourci pourNullable
ce qui représente unenullableversion du typeJ
. Un type est appelénullablesi des erreurs de compilation sontne pasjeté quand unnul
la valeur dudit type est tentée d'être utilisée. Pour plus de contexte surNullable
est, voir l'annexe ci-dessous.
Services d'enregistrement (ServiceDescriptor
s) avec
Jusqu'à présent, nous savons comment représenter les services (dépendances) commeServiceDescriptor
s. Nous allons maintenant apprendre à créer un conteneur de gestion et à enregistrer nos services auprès dudit conteneur.
Une instance de typeIServiceCollection
est ce qui représentera notre conteneur de gestion. Microsoft nous fournit une implémentation de cette interface – le classe de laMicrosoft.Extensions.DependencyInjectionMicrosoft.Extensions.DependencyInjection
namespace- nous n'avons donc pas à nous occuper de l'implémentation nous-mêmes.
Nous ne nous soucierons pas trop de la façon dont Microsoft implémenteIServiceCollection
cependant – ce qui compte vraiment, c'est ce queIServiceCollection
spécifie comme interface.
De sa définition d'interface, nous pouvons voir queIServiceCollection
spécifie l'extension à partir de trois interfaces qui s'étendent elles-mêmesCollection
et pas plus:
en utilisantMicrosoft.Extensions.DependencyInjection;
en utilisant Système.Collections.Générique;
interface publique IServiceCollection:ICollectionServiceDescriptor>,IEnumerableServiceDescriptor>,IListeServiceDescriptor> { }
Ainsi, on voit queIServiceCollection
est essentiellement une interface avecCollection
.
(Alors,IServiceCollection
est interprété comme «Je{ServiceCollection}
», qui signifie « interface à un ensemble de services », et non «{IService}Collection
», ce qui signifierait « collection d'interfaces aux services » !).
Enregistrement de service via des méthodes d'extension pourIServiceCollection
Afin de stocker des services dans unIServiceCollection
nous devons activer l'accès à certainsméthodes d'extension pour IServiceCollection
.
(Une méthode d'extension est une méthode d'instance d'une classe qui est ajoutée à la classeaprès la classe est définie. Confusément, vouspouvez ajouter des méthodes d'extension, qui sont des méthodes non abstraites, à une interface. Pour en savoir plus sur les méthodes d'extension, voir l'annexe ci-dessous).
Pour obtenir l'accès aux méthodes d'extension dont nous avons besoin, incluez simplement unà l'aide de Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions
déclaration en haut du fichier.
Quelques méthodes d'extension importantes (avec le paramètre pour la classe étendue,IServiceCollection
omis) ajouté parServiceCollectionServiceExtensions
sont:
AjouterSingleton(Taper type de service,Taper type d'implémentation);
AjouterSingleton(Taper type de service);
Étant des méthodes d'extension pourIServiceCollection
ces méthodes sont invoquées sur une instance de typeIServiceCollection
de la même manière que les méthodes d'instance habituelles. Par exemple, siprestations de service
a le genreIServiceCollection
alors nous appellerions les méthodes ci-dessus en écrivant
prestations de service.AjouterSingleton(type de service,type d'implémentation);
prestations de service.AjouterSingleton(type de service);
Vous pouvez probablement supposer que ces deux méthodes ajoutent unServiceDescriptor
de vieSingleton
qui a le spécifiétype de service
ettype d'implémentation
à laIServiceCollection
.
Il existe également des versions des méthodes ci-dessus pour lesquelles certaines combinaisons de paramètres sont maintenues fixes :
AjouterSingletonService,Mise en œuvre>();
AjouterSingletonService>();
AjouterSingletonService,Mise en œuvre>(FonctionIServiceProvider,Mise en œuvre> implémentationUsine)
Et, bien sûr, pour chaque méthode dont le nom estAjouterSingleton
il y aura des méthodes correspondantes avec des noms deAjouter un transitoire
etAjouter une portée
qui effectuent la même tâche pour les services deTransitoire
etPortée
vies, respectivement.
Cependant, ces deux versions deAjouterSingleton
qui spécifient l'instance qui doit être encapsulée par le service singleton, n'ont pasAjouter un transitoire
ouAjouter une portée
homologues, car cela n'aurait aucun sens de ne spécifier qu'une seule instance pourAjouter un transitoire
ouAjouter une portée
:
AjouterSingleton(Taper type de service,objet instance de mise en œuvre);
AjouterSingletonService>(Service exemple);
Résolution des services lors de l'exécution avecIServiceProvider
À ce stade, nous savons comment nous savons comment représenter les services (dépendances) commeServiceDescriptor
s, comment créer comment créer un conteneur de gestion et comment enregistrer nos services avec le conteneur. Le dernier élément que nous devons aborder est celui de la configuration de la résolution des services au moment de l'exécution (c'est-à-dire la configuration de l'injection des dépendances au moment de l'exécution).
Supposons queprestations de service
est unIServiceCollection
(c'est-à-dire un conteneur de gestion) qui contient certainsServiceDescriptor
s (c'est-à-dire « services » ou dépendances).
Pour récupérer des services à partir d'un conteneur de gestion nomméprestations de service
à l'exécution, nous allons d'abord obtenir une instance de typeIServiceProvider
du conteneur de gestion*en stockant la valeur de retour deservices.BuildServiceProvider()
. Ensuite, nous récupérons un service particulier en utilisant la seule méthode abstraite spécifiée dans leIServiceProvider
interface:
Publique objet? ObtenirService(Taper type de service)
*services.BuildServiceProvider
renvoie une instance deMicrosoft.Extensions.DependencyInjection.ServiceProviderMicrosoft.Extensions.DependencyInjection.ServiceProvider
qui implémenteIServiceProvider
.
Cette section est assez facultative.
Si vous en avez déjà unIServiceCollection
instance et correspondantIServiceProvider
et vous voulez en créer un autreIServiceCollection
instance en utilisant les dépendances stockées dans le premierIServiceCollection
exemple, vous pouvez utiliser ces méthodes d'extension pourIServiceCollection
:
AjouterSingleton(Taper type de service,FonctionIServiceProvider,objet> usine);
AjouterSingletonService,Mise en œuvre>(FonctionIServiceProvider,Mise en œuvre> implémentationUsine);
AjouterSingletonService>(FonctionIServiceProvider,Service> implémentationUsine);
Cette annexe documente certaines fonctionnalités moins connues du langage C#.
? et les types de référence nullables
UNEnon nullabletype est un type pour lequel des erreurs de compilation sont générées lorsqu'une variable de ce type avecnul
la valeur est tentée d'être utilisée. A l'opposé, unnullable type est un type pour lequel les erreurs du compilateur ne sont pas levées dans ladite situation. Vous pouvez toujours obtenir des erreurs d'exécution avec des types nullables, bien sûr ! L'intérêt des types non nullables est d'éviter les erreurs d'exécution en les interceptant à la compilation.
Selon le , tous les types de référence acceptaient la valeur Null avant C# 8.0. De nos jours (c'est-à-dire après C# 8.0), tous les types de référence sont non nullables par défaut.
Cependant, vous pouvez toujours utiliser des types nullables si vous le souhaitez vraiment. Pour tout typeJ
le typeNullable
est nullable.?T
est un raccourci pourNullable
.
Méthodes d'extension
En C#, il est possible de définir des méthodes d'instance en dehors de la définition de classe correspondante. Les méthodes ainsi définies sont appeléesméthodes d'extension.
Les méthodes d'extension doivent être définies dans unstatique
classe, et doit utiliser lace
mot-clé de la manière suivante :
Publique classer Cls{ ... }
Publique statique classer Extension
{
Publique statique entier extensionMethod1(ce Cls CL)
{entier uneValeur = 0;retourner uneValeur ; }
Publique statique entier extensionMethod2(ce Cls CL,entier argument)
{entier uneValeur = 0;retourner uneValeur ; }
}
Les méthodes d'extension sont appelées de la même manière que les méthodes d'instance régulières : pour appeler les méthodes d'extension définies ci-dessus sur une instanceCL
deCls
tu écriraiscls.extensionMethod()
oucls.extensionMethod2(arg)
respectivement.
Méthodes d'extension aux interfaces
De manière quelque peu déroutante, il est possible de définir des méthodes d'extension – exactement de la même manière que ci-dessus – pour les interfaces. Pour moi, cette possibilité va à l'encontre de l'intention de "l'interface" – les interfaces ne sont pas censées être associées à des implémentations réelles de méthodes. Mais toipouvez fais le. Il est également en fait impossible d'ajouter quelque chose comme une "méthode d'extension abstraite" à une interface. La bibliothèque standard C # utilise malheureusement beaucoup l'implémentation d'interfaces via des méthodes d'extension. Tant pis.
J'ai fait référence aux deux articles suivants pour développer ma compréhension deIServiceCollection
:,.
Source link