Implémentation correcte d'IDisposable
L'interface utilisée pour libérer les ressources objet dans .NET est IDisposable, mais l'implémentation de cette interface est assez particulière. De plus, il existe deux fonctions : Finalize et Close.
MSDN recommande d'implémenter l'interface IDisposable selon le modèle suivant :
1 classe publique Foo : IDisposable
2 {
3 public void Disposer()
4 {
5 Disposer(vrai);
6 GC.SuppressFinalize(this);
7}
8
9 vide virtuel protégé Dispose (bool disposition)
10 {
11 si (!m_disposed)
12 {
13 si (disposer)
14 {
15 // Libérer les ressources gérées
16}
17
18 // Libérer les ressources non gérées
19
20 m_disposé = vrai ;
vingt-et-un }
vingt-deux }
vingt-trois
24 ~Foo()
25 {
26 Disposer(faux);
27}
28
29 bool privé m_disposed ;
30}
31
32
Il existe en fait deux fonctions pour libérer des ressources dans les objets .NET : Dispose et Finalize. Le but de Finalize est de libérer les ressources non gérées, tandis que Dispose est utilisé pour libérer toutes les ressources, y compris celles gérées et non gérées.
Dans ce mode, la fonction void Dispose(bool disposing) utilise un paramètre de disposition pour distinguer si elle est actuellement appelée par Dispose(). S'il est appelé par Dispose(), les ressources gérées et non gérées doivent être libérées en même temps. S'il est appelé par ~Foo() (c'est-à-dire Finalize() de C#), il vous suffit alors de libérer les ressources non gérées.
En effet, la fonction Dispose() est explicitement appelée par un autre code et nécessite la libération de ressources, tandis que Finalize est appelée par le GC. Les autres objets gérés référencés par Foo n'auront peut-être pas besoin d'être détruits lorsque le GC est appelé, et même s'ils sont détruits, ils seront appelés par le GC. Par conséquent, seules les ressources non gérées doivent être libérées dans Finalize. D'un autre côté, puisque les ressources gérées et non gérées ont été libérées dans Dispose(), il n'est pas nécessaire d'appeler à nouveau Finalize lorsque l'objet est recyclé par GC, appelez donc GC.SuppressFinalize(this) dans Dispose() pour éviter la duplication. Finaliser.
Cependant, il n'y a aucun problème même si Finalize et Dispose sont appelés à plusieurs reprises, du fait de l'existence de la variable m_disposed, la ressource ne sera libérée qu'une seule fois et les appels redondants seront ignorés.
Par conséquent, le modèle ci-dessus garantit :
1. Finalize libère uniquement les ressources non gérées ;
2. Supprimez les ressources gérées et non gérées ;
3. Il n'y a aucun problème à appeler Finalize et Dispose à plusieurs reprises ;
4. Finalize et Dispose partagent la même stratégie de libération de ressources, il n'y a donc aucun conflit entre eux ; .
En C#, ce modèle doit être implémenté explicitement, où la fonction ~Foo() de C# représente Finalize(). En C++/CLI, ce mode est automatiquement implémenté, mais le destructeur de classe C++ est différent.
Selon la sémantique C++, le destructeur est appelé lorsqu'il sort de la portée ou est supprimé. En C++ managé (c'est-à-dire en C++ managé dans .NET 1.1), le destructeur est équivalent à la méthode Finalize() dans le CLR, qui est appelée par le GC lors du garbage collection. Par conséquent, le moment de l'appel n'est pas clair. En C++/CLI dans .NET 2.0, la sémantique des destructeurs a été modifiée pour être équivalente à la méthode Dispose(), ce qui implique deux choses :
1. Toutes les classes CLR en C++/CLI implémentent l'interface IDisposable, vous pouvez donc utiliser le mot-clé using pour accéder aux instances de cette classe en C#.
2. Le destructeur n'est plus équivalent à Finalize().
Pour le premier point, c'est une bonne chose, je pense que sémantiquement Dispose() est plus proche du destructeur C++. Concernant le deuxième point, Microsoft a réalisé une extension en introduisant la fonction "!", comme indiqué ci-dessous :
1 public ref class Foo
2 {
3 publics :
4Foo();
5 ~Foo(); // destructeur
6 !Foo(); // finaliseur
7} ;
8
La fonction "!" (je ne sais vraiment pas comment l'appeler) remplace l'original Finalize() en Managed C++ et est appelée par le GC. MSDN recommande que, afin de réduire la duplication de code, vous puissiez écrire du code comme ceci :
1 ~Foo()
2 {
3 //Libérer les ressources gérées
4 ceci->!Foo();
5}
6
7 !Foo()
8 {
9 //Libérer les ressources non gérées
10}
11
Pour la classe ci-dessus, le code C# correspondant généré par C++/CLI est en fait le suivant :
1 cours public Foo
2 {
3 vide privé !Foo()
4 {
5 // Libérer les ressources non gérées
6}
7
8 vide privé ~Foo()
9 {
10 // Libérer les ressources gérées
11 !Foo();
12}
13
14 publicFoo()
15 {
16}
17
18 public void Disposer()
19 {
20 Disposer(vrai);
21 GC.SuppressFinalize(this);
vingt-deux }
vingt-trois
24 vide virtuel protégé Dispose (bool disposition)
25 {
26 si (disposer)
27 {
28 ~Foo();
29 }
30 autres
31 {
32 essayer
33 {
34 !Foo();
35}
36 enfin
37 {
38 base.Finalize();
39 }
40}
41 }
42
43 void protégé Finalize()
44 {
45 Disposer(faux);
46 }
47 }
48
Puisque ~Foo() et !Foo() ne seront pas appelés à plusieurs reprises (du moins MS le pense), il n'y a pas de variables dans ce code qui soient identiques au m_disposed précédent, mais la structure de base est la même.
De plus, vous pouvez voir que ce ne sont pas réellement ~Foo() et !Foo() qui sont Dispose et Finalize, mais le compilateur C++/CLI génère deux fonctions Dispose et Finalize et les appelle au moment approprié. C++/CLI a en fait fait beaucoup de travail, mais le seul problème est qu'il repose sur l'appel de l'utilisateur !Foo() dans ~Foo().
Concernant la libération des ressources, la dernière chose à mentionner est la fonction Close. Elle est sémantiquement très similaire à Dispose. Selon MSDN, cette fonction est fournie pour que les utilisateurs se sentent plus à l'aise, car les utilisateurs sont plus habitués à appeler Close() pour certains objets, tels que les fichiers.
Cependant, après tout, ces deux fonctions font la même chose, le code recommandé par MSDN est donc :
1 public vide Fermer()
2 {
3 Supprimer(();
4}
5
6
Ici, la fonction Dispose sans paramètres est appelée directement pour obtenir la même sémantique que Dispose. Cela semble parfait, mais d'un autre côté, si Dispose et Close sont fournis en même temps, cela apportera une certaine confusion aux utilisateurs. Sans voir les détails du code, il est difficile de connaître la différence entre ces deux fonctions. Par conséquent, les spécifications de conception du code .NET indiquent que les utilisateurs ne peuvent en réalité utiliser qu’une seule de ces deux fonctions. Le modèle suggéré est donc :
1 classe publique Foo : IDisposable
2 {
3 public vide Fermer()
4 {
5 Supprimer();
6}
7
8 vide IDisposable.Dispose()
9 {
10 Disposer(vrai);
11 GC.SuppressFinalize(this);
12}
13
14 vide virtuel protégé Dispose (bool disposition)
15 {
16 // Comme avant
17}
18}
19
Une implémentation dite explicite de l'interface est utilisée ici : void IDisposable.Dispose(). Cette implémentation explicite n'est accessible que via l'interface, mais pas via la classe d'implémentation. donc:
1 Foo foo = nouveau Foo();
2
3 foo.Dispose(); // Erreur
4 (foo comme IDisposable).Dispose(); // Correct
5
Cela prend soin des deux. Pour ceux qui aiment utiliser Close, ils peuvent utiliser foo.Close() directement et ils ne verront pas Dispose(). Pour ceux qui aiment Dispose, il peut convertir le type en IDisposable pour l'appeler ou utiliser l'instruction using. Grande joie à tous les deux !
http://www.cnblogs.com/xlshcn/archive/2007/01/16/idisposable.html