Question

J'ai une question, et je vais marquer cette subjective puisque c'est ce que je pense qu'il évolue dans plus d'une discussion. J'espère que quelques bonnes idées ou des pensées provocateurs. Je présente mes excuses pour la question de longue haleine, mais vous devez connaître le contexte.

La question est essentiellement:

  • Comment gérez-vous les types concrets en ce qui concerne les conteneurs IoC? Plus précisément, qui est responsable de les disposer, si elles doivent être éliminés, et comment cette connaissance se propage vers le code d'appel?

Avez-vous besoin d'eux pour être IDisposable? Dans le cas contraire, est-ce à l'épreuve de code, ou est la règle que vous ne pouvez pas utiliser des objets jetables? Si vous appliquez-IDisposable exigences sur les interfaces et les types de béton pour être à l'épreuve, dont la responsabilité est injecté des objets dans le cadre des appels de constructeur?


Modifier : J'ai accepté la réponse par Ballard @ Chris car il est le plus proche un à l'approche que nous avons fini avec.

En fait, nous revenons toujours un type qui ressemble à ceci:

public interface IService<T> : IDisposable
    where T: class
{
    T Instance { get; }
    Boolean Success { get; }
    String FailureMessage { get; } // in case Success=false
}

Nous revenons ensuite un objet qui implémente cette interface de retour à la fois .Resolve et .TryResolve, de sorte que ce que nous obtenons dans le code d'appel est toujours le même type.

, l'objet d'implémentation de cette interface, IService<T> est IDisposable, et devrait toujours être mis au rebut. Ce n'est pas au programmeur qui résout un service de décider si l'objet IService<T> doit être mis au rebut ou non.

Cependant, ce qui est la partie essentielle, si l'instance de service doit être mis au rebut ou non, que la connaissance est cuit dans l'objet d'implémentation IService<T>, donc si c'est un service scope usine (ie. Chaque appel à Resolve se termine avec une nouvelle instance de service), alors l'instance de service seront éliminés lorsque l'objet est disposé IService<T>.

a également permis de soutenir d'autres champs d'application spécifiques, comme la mise en commun. Nous pouvons maintenant dire que nous voulons un minimum de 2 instances de service, maximum 15, et typiquement 5, ce qui signifie que chaque appel à .Resolve sera soit récupérer une instance de service à partir d'un pool d'objets disponibles, ou construire un nouveau. Et puis, lorsque l'objet IService<T> qui détient le service mis en commun est éliminé, l'instance de service est relâché dans sa piscine.

Bien sûr, cela fait tout regard de code comme ceci:

using (var service = ServiceContainer.Global.Resolve<ISomeService>())
{
    service.Instance.DoSomething();
}

mais il est une approche propre, et il a la même syntaxe quel que soit le type de service ou objet concret en cours d'utilisation, donc nous avons choisi cela comme une solution acceptable.


suit Question originale, pour la postérité


est ici question de longue haleine:

Nous avons un conteneur IoC que nous utilisons, et nous avons récemment découvert ce qui équivaut à un problème.

Dans le code non IoC, quand nous voulions utiliser, par exemple, un fichier, nous avons utilisé une classe comme ceci:

using (Stream stream = new FileStream(...))
{
    ...
}

Il n'y avait pas question de savoir si cette classe était quelque chose qui a tenu une ressource limitée ou non, car nous savions que les dossiers devaient être fermés, et la classe elle-même mis en œuvre IDisposable. La règle est tout simplement que chaque classe, nous construisons un objet de, qui implémente IDisposable, doit être éliminé. Aucune question posée. Ce n'est pas à l'utilisateur de cette classe pour décider si appeler Dispose est en option ou non.

Ok, donc à la première étape vers le conteneur IoC. Supposons que nous ne voulons pas le code pour parler directement au fichier, mais plutôt passer par une couche d'indirection. Appelons cette classe un BinaryDataProvider pour cet exemple. En interne, la classe utilise un flux, ce qui est encore un objet jetable, donc serait changé le code ci-dessus:

using (BinaryDataProvider provider = new BinaryDataProvider(...))
{
    ...
}

Cela ne change pas grand-chose. La connaissance que la classe implémente IDisposable est toujours là, sans poser de questions, nous devons appeler Dispose.

Mais, supposons que nous avons des classes qui fournissent des données qui n'utilisent actuellement pas de telles ressources limitées.

Le code ci-dessus pourrait alors être écrit:

BinaryDataProvider provider = new BinaryDataProvider();
...

OK, tout va bien, mais ici vient la viande de la question. Supposons que nous voulons utiliser un conteneur IoC pour injecter ce fournisseur au lieu de dépendre d'un type concret spécifique.

Le code serait alors:

IBinaryDataProvider provider =
    ServiceContainer.Global.Resolve<IBinaryDataProvider>();
...

Notez que je suppose qu'il ya une interface indépendante disponible que nous pouvons accéder à l'objet à travers.

Avec le changement ci-dessus, si nous voulons plus tard utiliser un objet qui devrait vraiment être éliminé? Aucun du code existant qui résout cette interface est écrite de disposer de l'objet, alors maintenant?

La façon dont nous le voyons, nous devons choisir une solution:

  • Mettre en œuvre le contrôle d'exécution qui vérifie que si un type de béton qui est en cours d'enregistrement met en œuvre IDisposable, exige que l'interface est exposée par le biais met également en œuvre IDisposable. Ce n'est pas une bonne solution
  • Enfore une contrainte sur les interfaces utilisées, ils doivent toujours hériter de IDisposable, afin d'être à l'épreuve
  • Imposer l'exécution qu'aucun type de béton peuvent être IDisposable, car cela est spécifiquement pas gérée par le code en utilisant le conteneur IoC
  • Il suffit de le laisser au programmeur de vérifier si l'objet implémente IDisposable et « faire la bonne chose »?
  • Y at-il d'autres?

En outre, qu'en injectant des objets dans les constructeurs? Notre récipient, et quelques-uns des autres conteneurs que nous avons examiné, est capable d'injecter un nouvel objet dans un paramètre à un constructeur d'un type de béton. Par exemple, si notre BinaryDataProvider besoin d'un objet qui implémente l'interface ILogging, si nous imposons IDispose- « capacité » sur ces objets, dont la responsabilité est-il de disposer de l'objet de l'enregistrement?

Que pensez-vous? Je veux des opinions, bonnes et mauvaises.

Était-ce utile?

La solution

(Avertissement:.. Je réponds à cette base sur des choses java Bien que je programme C # Je ne l'ai pas quoi que ce soit en C approximé # mais je sais qu'il est possible Désolé sur la terminologie java)

Vous pouvez laisser le cadre IoC inspecter l'objet construit pour voir si elle prend en charge IDisposable. Sinon, vous pouvez utiliser un proxy dynamique pour envelopper l'objet réel que le cadre IoC fournit au code client. Cette procuration dynamique pourrait mettre en œuvre IDisposable, de sorte que vous souhaitez toujours livrer un IDisposable au client. Tant que vous travaillez avec des interfaces qui devrait être assez simple?

Ensuite, vous auriez tout simplement avoir le problème de communication avec le développeur lorsque l'objet est un IDisposable. Je ne suis pas sûr de savoir comment être this'd fait d'une manière agréable.

Autres conseils

Une option pourrait être d'aller avec un modèle d'usine, de sorte que les objets créés directement par le conteneur IoC besoin de ne jamais être eux-mêmes éliminés, par exemple

IBinaryDataProviderFactory factory =
    ServiceContainer.Global.Resolve<IBinaryDataProviderFactory>();
using(IBinaryDataProvider provider = factory.CreateProvider())
{
    ...
}

Le seul inconvénient est ajouté la complexité, mais il ne signifie pas que le conteneur ne crée jamais rien que le développeur est censé disposer -. Il est toujours le code explicite qui fait cela

Si vous voulez vraiment rendre évidente, la méthode de l'usine pourrait être nommé quelque chose comme CreateDisposableProvider ().

En fait, vous est venu avec une solution très sale: votre contrat de IService viole le SRP , Wich est un gros no-no.

Ce que je recommande est de distinguer ce qu'on appelle les services « singleton » de services dits « prototypes ». Durée de vie de ceux « singleton » est géré par le conteneur, qui peut interroger à l'exécution si une instance particulière implémente IDisposable et invoquer Dispose() sur arrêt si oui.

Gestion de prototypes, d'autre part, est tout à fait la responsabilité du code d'appel.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top