Étant principalement un développeur C ++, l’absence de RAII (Resource Acquisition Is Initialization) en Java et .NET m’a toujours dérangé. Le fait que l’obligation de nettoyage soit déplacée de l’écrivain à son consommateur (au moyen de la using
d’ try finally
ou de la construction using
.NET) semble être nettement inférieure.
Je vois pourquoi en Java il n’ya pas de support pour RAII puisque tous les objects se trouvent sur le tas et que le ramasse-miettes ne supporte pas de manière insortingnsèque la destruction déterministe, mais en .NET avec l’introduction de types de valeur ( struct
) ) candidat idéal pour RAII. Un type de valeur créé sur la stack a une scope bien définie et la sémantique du destructeur C ++ peut être utilisée. Cependant, le CLR ne permet pas à un type de valeur d’avoir un destructeur.
Mes recherches aléatoires ont trouvé un argument selon lequel si un type de valeur est encadré, il tombe sous la juridiction du ramasse-miettes et sa destruction devient donc non déterministe. Je pense que cet argument n’est pas assez fort, les avantages de RAII sont assez grands pour dire qu’un type de valeur avec un destructeur ne peut pas être encadré (ou utilisé comme un membre de classe).
Bref, ma question est la suivante : existe-t-il d’autres raisons pour lesquelles les types de valeurs ne peuvent pas être utilisés pour introduire RAII dans .NET? (ou penses-tu que mon argument concernant les avantages évidents de RAII est erroné?)
Edit: Je n’ai pas dû formuler clairement la question puisque les quatre premières réponses ont manqué le point. Je connais Finalize
et ses caractéristiques non déterministes, je connais le using
et je pense que ces deux options sont inférieures à RAII. using
est une autre chose que le consommateur d’une classe doit se rappeler (combien de personnes ont oublié de mettre un StreamReader
dans un bloc d’ using
?). Ma question est philosophique sur la conception du langage, pourquoi est-ce ainsi et peut-elle être améliorée?
Par exemple, avec un type de valeur destructible déterministe générique, je peux rendre les mots-clés d’ using
et de lock
redondants (réalisables par les classes de bibliothèque):
public struct Disposer where T : IDisposable { T val; public Disposer(T t) { val = t; } public T Value { get { return val; } } ~Disposer() // Currently illegal { if (val != default(T)) val.Dispose(); } }
Je ne peux pas m’empêcher de terminer par une citation à propos de ce que j’ai vu une fois mais que je ne trouve pas actuellement l’origine.
Vous pouvez prendre ma destruction déterministe lorsque ma main froide sort de son champ de vision. – Anon
Un meilleur titre serait “Pourquoi n’y a-t-il pas de RAII en C # / VB”. C ++ / CLI (L’évolution de l’avortement qui était géré C ++) a RAII exactement dans le même sens que C ++. Tout n’est que du sucre syntaxique pour le même modèle de finalisation que le rest des langages CLI utilisent (les destructeurs dans les objects gérés pour C ++ / CLI sont effectivement des finaliseurs), mais c’est là.
Vous pourriez aimer http://blogs.msdn.com/hsutter/archive/2004/07/31/203137.aspx
Excellente question et une qui m’a beaucoup dérangé. Il semble que les avantages de RAII soient perçus de manière très différente. D’après mon expérience avec .NET, l’absence de collecte de ressources déterministe (ou du moins fiable) est l’un des principaux inconvénients. En fait, .NET m’a obligé à plusieurs resockets à utiliser des architectures complètes pour gérer des ressources non gérées qui pourraient (mais pas nécessairement) nécessiter une collecte explicite. Ce qui, bien sûr, est un inconvénient majeur car cela rend l’architecture globale plus difficile et détourne l’attention du client des aspects plus centraux.
Brian Harry a un bon billet sur les raisons ici .
Voici un extrait:
Qu’en est-il de la finalisation déterministe et des types de valeur (structs)?
————– J’ai vu beaucoup de questions sur les structs ayant des destructeurs, etc. Il existe une variété de problèmes pour lesquels certaines langues ne les ont pas.
(1) composition – Ils ne vous donnent pas de durée de vie déterministe dans le cas général pour les mêmes types de composition décrits ci-dessus. Toute classe non déterministe contenant une classe n’appelait pas le destructeur tant que le GC ne l’aurait pas finalisé.
(2) copier des constructeurs – Le seul endroit où il serait vraiment agréable est dans les locaux affectés par stack. Ils seraient à la scope de la méthode et tout irait bien. Malheureusement, pour que cela fonctionne vraiment, vous devez également append des constructeurs de copie et les appeler chaque fois qu’une instance est copiée. C’est l’une des choses les plus laides et les plus complexes du C ++. Vous finissez par obtenir du code en cours d’exécution partout où vous ne l’attendez pas. Cela cause des problèmes de langue. Certains concepteurs de langage ont choisi de restr à l’écart de cela.
Disons que nous avons créé des structures avec des destructeurs, mais avons ajouté un ensemble de ressortingctions pour rendre leur comportement plus sensible face aux problèmes ci-dessus. Les ressortingctions seraient quelque chose comme:
(1) Vous ne pouvez les déclarer qu’en tant que variables locales.
(2) Vous ne pouvez les passer que par-ref
(3) Vous ne pouvez pas les affecter, vous pouvez uniquement accéder aux champs et appeler des méthodes sur eux.
(4) Vous ne pouvez pas les encadrer.
(5) Problèmes d’utilisation avec Reflection (liaison tardive) car cela implique généralement la boxe.
peut-être plus, mais c’est un bon début.
A quoi serviraient ces choses? Voulez-vous créer un fichier ou une classe de connexion à une firebase database qui peut UNIQUEMENT être utilisée comme variable locale? Je ne crois pas que quiconque le ferait vraiment. Ce que vous feriez à la place, c’est créer une connexion à usage général, puis créer un wrapper auto-détruit à utiliser comme variable locale de scope. L’appelant choisirait alors ce qu’il voulait utiliser. Notez que l’appelant a pris une décision et qu’il n’est pas entièrement encapsulé dans l’object même. Étant donné que vous pourriez utiliser quelque chose comme les suggestions à venir dans quelques sections.
Le remplacement de RAII dans .NET est le pattern-use, qui fonctionne presque aussi bien lorsque vous vous y êtes habitué.
Le plus proche de vous est l’opérateur stackalloc très limité.
Il y a des threads similaires si vous les recherchez, mais en gros, si vous voulez RAII, .NET implémente simplement un type IDisposable et utilise l’instruction “using” pour obtenir une élimination déterministe. De cette façon, beaucoup des mêmes idées peuvent être mises en œuvre et utilisées de manière légèrement plus verbale.
IMHO, les grandes choses dont VB.net et C # ont besoin sont:
Tout cela peut être très bien lu dans vb.net, et un peu moins bien dans C #, mais un support de première classe améliorerait les deux langues.
Vous pouvez faire une forme de RAII dans .net et java en utilisant les méthodes finalize (). Une surcharge finalize () est appelée avant que la classe ne soit nettoyée par le GC et peut donc être utilisée pour nettoyer les ressources qui ne devraient absolument pas être conservées par la classe (mutex, sockets, descripteurs de fichiers, etc.). Ce n’est toujours pas déterministe.
Avec .NET, vous pouvez faire certaines choses de manière déterministe avec l’interface IDisposable et le mot-clé using, mais cela présente des limitations (utilisation de la construction lorsqu’elle est requirejse pour un comportement déterministe, pas de désallocation de mémoire déterministe, pas automatiquement utilisée dans les classes, etc.).
Et oui, je pense qu’il y a une place pour l’introduction des idées RAII dans .NET et d’autres langages gérés, bien que le mécanisme exact puisse être débattu sans fin. La seule autre solution que je pourrais voir serait d’introduire un GC capable de gérer un nettoyage arbitraire des ressources (pas seulement de la mémoire), mais vous rencontreriez des problèmes lorsque ces ressources devaient absolument être libérées de manière déterministe.