WeakRefDictionary 类是一个弱引用字典,在该类中 Microsoft 为我们示范了 .NET 中弱引用的用法,希望大家在看这个类时能够注意到这一点。
另外,在 ObjectBuilder 中,默认实现的“定位器”也是使用该类作为“底层存储”,它最主要的特点就是不会阻止 CLR 回收该字典中的对象(只要指向该对象的强引用变量的数量为 0)。
1using System;
2using System.Collections.Generic;
3using Microsoft.Practices.ObjectBuilder.Properties;
4
5namespace Microsoft.Practices.ObjectBuilder
6{
7 /**//// <summary>
8 /// 表示使用 "弱引用"
9 /// 保存 "键/值" 中的
10 /// 值的字典。该类支
11 /// 持空引用。
12 /// </summary>
13 /// <remarks>
14 /// <para>
15 /// 该类是一个 "弱引
16 /// 用" 字典类。添加
17 /// 到该类中的对象不
18 /// 会因为该字典中保
19 /// 存了指向该对象的
20 /// 引用变量而不被垃
21 /// 圾收集,换句话说:
22 /// 即使某个对象已经
23 /// 被添加到该字典中,
24 /// CLR 仍然会对该对
25 /// 象执行垃圾收集(
26 /// 如果有其它引用指
27 /// 向该对象,不能被
28 /// 垃圾收集)。
29 /// </para>
30 /// <para>
31 /// 该类是对 <see cref="Dictionary{TKey,TValue}"/>
32 /// 的一个封装,并且
33 /// 它们的使用方式也
34 /// 是一样的,唯一的
35 /// 区别是保存在该字
36 /// 典中的对象会被执
37 /// 行垃圾收集并被清
38 /// 理出字典。下面是
39 /// 该类执行自动清理
40 /// 的时机:
41 ///
42 /// 1、获取 Count属性值
43 /// 时。
44 ///
45 /// 2、调用 TryGet 函数
46 /// 获取指定键的对象
47 /// 时。
48 /// </para>
49 /// <para>
50 /// "弱引用" 的使用
51 /// 技巧:
52 ///
53 /// 1、要操作一个 "弱引
54 /// 用" 指向的对象,
55 /// 应用程序须获取该
56 /// 对象的一个 "强引
57 /// 用" ,然后使用这
58 /// 个 "强引用" 变量
59 /// 操作对象。
60 ///
61 /// 2、一旦创建了一个对
62 /// 象的"弱引用",开
63 /// 发人员通常需要将
64 /// 该对象的 "强引用"
65 /// 设置为 null ,否
66 /// 则 CLR将不会对该
67 /// 对象执行垃圾收集。
68 ///
69 /// 3、不要直接使用 "弱
70 /// 引用" 的 Target
71 /// 属性操作对象,CLR
72 /// 无法保证两次调用
73 /// Target 属性期间
74 /// 不进行垃圾收集。
75 /// 而一般情况下开发
76 /// 人员希望不执行垃
77 /// 圾收集。
78 /// </para>
79 /// </remarks>
80 /// <typeparam name="TKey">
81 /// 键的类型。
82 /// </typeparam>
83 /// <typeparam name="TValue">
84 /// 值的类型。
85 /// </typeparam>
86 public class WeakRefDictionary<TKey, TValue>
87 {
88
89 字段#region 字段
90 /**//// <summary>
91 /// 内部使用的存储结
92 /// 构,是一个"键/弱
93 /// 引用" 对的字典。
94 /// </summary>
95 private Dictionary<TKey, WeakReference> inner = new Dictionary<TKey, WeakReference>();
96 #endregion
97
98 属性#region 属性
99 /**//// <summary>
100 /// 获取包含在字典中
101 /// 的项的数目。
102 /// </summary>
103 /// <remarks>
104 /// 由于使用 "弱引用"
105 /// 保存该字典中的项,
106 /// 因此不能保证该属
107 /// 性值与通过枚举得
108 /// 到的项的数量相同。
109 /// 应该把 Count 当
110 /// 作一个估算值。
111 /// </remarks>
112 public int Count
113 {
114 get
115 {
116 CleanAbandonedItems();
117 return inner.Count;
118 }
119 }
120 /**//// <summary>
121 /// 检索字典中的值。
122 /// </summary>
123 /// <param name="key">要查找的键。</param>
124 /// <returns>字典中的值。</returns>
125 /// <exception cref="KeyNotFoundException">
126 /// 当字典中不存在 key
127 /// 时引发。由于该字
128 /// 典中包含的是 "弱
129 /// 引用",因此该 key
130 /// 可能由于对象被垃
131 /// 圾回收而被清理。
132 /// </exception>
133 public TValue this[TKey key]
134 {
135 get
136 {
137 TValue result;
138 bool succeed = TryGet(key, out result);
139
140 if (!succeed)
141 {
142 throw new KeyNotFoundException();
143 }
144
145 return result;
146 }
147 }
148 #endregion
149
150 私有方法#region 私有方法
151 /**//// <remarks>
152 /// 对 "弱引用" 指向
153 /// 的对象进行编码。
154 /// </remarks>
155 private object EncodeNullObject(object value)
156 {
157 if (value == null)
158 {
159 return typeof(NullObject);
160 }
161
162 return value;
163 }
164 /**//// <summary>
165 /// 对 "弱引用" 指向
166 /// 的对象进行解码。
167 /// </summary>
168 private TObject DecodeNullObject<TObject>(object innerValue)
169 {
170 /**//*
171 * 在应用程序运行时
172 * 候,CLR 会为程序
173 * 中用到的每个类型
174 * 创建一个内部的数
175 * 据结构和 Type 对
176 * 象,在整个程序运
177 * 行期间它们都不会
178 * 被销毁。我们在程
179 * 序中使用 object.GetType();
180 * 等方法获取的都是
181 * 指向相同 Type 对
182 * 象的引用变量(对
183 * 同一个类型而言)。
184 */
185 if (innerValue == typeof(NullObject))
186 {
187 return default(TObject);
188 }
189
190 return (TObject)innerValue;
191 }
192 /**//// <summary>
193 /// 清理字典中被垃圾
194 /// 回收的项。
195 /// </summary>
196 /// <remarks>
197 /// 当 "弱引用" 指向
198 /// 的对象为空引用时,
199 /// 则表示该对象已经
200 /// 被垃圾回收。这面
201 /// 里有一个问题:如
202 /// 何让 "弱引用" 指
203 /// 向一个空引用?这
204 /// 里通过对 "弱引用"
205 /// 指向的对象进行编
206 /// 码和解码来解决这
207 /// 个问题。
208 /// </remarks>
209 private void CleanAbandonedItems()
210 {
211 List<TKey> deadKeys = new List<TKey>();
212
213 foreach (KeyValuePair<TKey, WeakReference> kvp in inner)
214 {
215 if (kvp.Value.Target == null)
216 {
217 deadKeys.Add(kvp.Key);
218 }
219 }
220
221 foreach (TKey key in deadKeys)
222 {
223 inner.Remove(key);
224 }
225 }
226 #endregion
227
228 公有方法#region 公有方法
229 /**//// <summary>
230 /// 将指定的键和值添
231 /// 加到字典中。
232 /// </summary>
233 /// <param name="key">
234 /// 要添加的键。
235 /// </param>
236 /// <param name="value">
237 /// 要添加的值。
238 /// </param>
239 public void Add(
240 TKey key,
241 TValue value)
242 {
243 TValue dummy;
244
245 if (TryGet(key, out dummy))
246 {
247 throw new ArgumentException(Resources.KeyAlreadyPresentInDictionary);
248 }
249
250 /**//*
251 * 在该类里,由于指
252 * 向空引用的 "弱引
253 * 用" 对象被认为是
254 * 指向一个已经被垃
255 * 圾回收的对象,因
256 * 此这里不能使用空
257 * 引用创建 "弱引用"
258 * 对象。在这儿,通
259 * 过调用 EncodeNullObject
260 * 函数将空引用转换
261 * 为一个指向内部类
262 * (NullObject 类)
263 * 的类型对象的引用
264 * 并使用该引用创建
265 * 一个 "弱引用" 对
266 * 象来避免这种情况。
267 */
268 inner.Add(
269 key,
270 new WeakReference(EncodeNullObject(value)));
271 }
272 /**//// <summary>
273 /// 确定字典中是否包
274 /// 含指定键的值。
275 /// </summary>
276 /// <param name="key">
277 /// 要在字典中定位的
278 /// 键。
279 /// </param>
280 /// <returns>
281 /// 如果字典中包含指
282 /// 定键的项,则为 true;
283 /// 否则为 false。
284 /// </returns>
285 public bool ContainsKey(TKey key)
286 {
287 TValue dummy;
288 return TryGet(key, out dummy);
289 }
290 /**//// <summary>
291 /// 尝试获取与指定键
292 /// 相关联的值。
293 /// </summary>
294 /// <param name="key">
295 /// 要获取值的键。
296 /// </param>
297 /// <param name="value">
298 /// 如果找到该键,则
299 /// 该输出参数的值为
300 /// 与指定键相关联的
301 /// 值;否则,则返回
302 /// value 参数类型的
303 /// 默认值。
304 /// </param>
305 /// <returns>
306 /// 如果字典中包含指
307 /// 定键的项,则为 true;
308 /// 否则为 false。
309 /// </returns>
310 /// <remarks>
311 /// 如果与指定键相关
312 /// 联的对象已经被垃
313 /// 圾回收,则从字典
314 /// 中清理该项并返回
315 /// false(表示该操
316 /// 作执行失败)。
317 /// </remarks>
318 public bool TryGet(
319 TKey key,
320 out TValue value)
321 {
322 value = default(TValue);
323
324 WeakReference wr;
325 if (!inner.TryGetValue(key, out wr))
326 {
327 return false;
328 }
329
330 /**//*
331 * 如果走到这里,可
332 * 以确保字典中包含
333 * 指定键的项。这里
334 * 将其保存在 result
335 * 引用中可以确保在
336 * 该函数调用完成之
337 * 前 CLR 不对该对
338 * 象执行垃圾回收。
339 */
340 object result = wr.Target;
341
342 /**//*
343 * 如果 result 为空
344 * 引用,则表示 result
345 * 指向的对象已经被
346 * 垃圾回收。
347 */
348 if (result == null)
349 {
350 /**//*
351 * 移除已经被垃圾回
352 * 收的项。
353 */
354 inner.Remove(key);
355 return false;
356 }
357
358 /**//*
359 * 对于指向 NullObject
360 * 类型对象的引用,
361 * 应该把它还原为空
362 * 引用。
363 */
364 value = DecodeNullObject<TValue>(result);
365 return true;
366 }
367 /**//// <summary>
368 /// 从字典中移除指定
369 /// 键的项。
370 /// </summary>
371 /// <param name="key">
372 /// 要移除项的键。
373 /// </param>
374 /// <returns>
375 /// 如果成功移除该项,
376 /// 则为 true; 否则
377 /// 为 false。
378 /// </returns>
379 public bool Remove(TKey key)
380 {
381 return inner.Remove(key);
382 }
383 /**//// <summary>
384 /// 返回循环访问字典
385 /// 的枚举数。
386 /// </summary>
387 /// <returns>枚举数。</returns>
388 /// <remarks>
389 /// 由于字典中的对象
390 /// 可以从枚举数返回
391 /// 并被使用,因此这
392 /// 里会使用一个临时
393 /// 的强引用变量指向
394 /// 该对象来阻止在执
395 /// 行枚举期间的垃圾
396 /// 回收。注意:一旦
397 /// 枚举结束,那些临
398 /// 时的强引用将被释
399 /// 放。如果想继续阻
400 /// 止垃圾回收,则必
401 /// 须在枚举期间使用
402 /// 强引用变量指向这
403 /// 些对象。
404 /// </remarks>
405 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
406 {
407 foreach (KeyValuePair<TKey, WeakReference> kvp in inner)
408 {
409 /**//*
410 * 使用 innerValue
411 * 指向对象可以阻止
412 * CLR 执行垃圾回收。
413 * 这样可以确保在创
414 * 建 KeyValuePair
415 * 对象之前, "弱引
416 * 用" 指向的对象不
417 * 被垃圾回收。
418 */
419 object innerValue = kvp.Value.Target;
420
421 if (innerValue != null)
422 {
423 yield return new KeyValuePair<TKey, TValue>(kvp.Key, DecodeNullObject<TValue>(innerValue));
424 }
425 }
426 }
427 #endregion
428
429 NullObject 类#region NullObject 类
430 /**//// <summary>
431 /// 内部类,该类只能
432 /// 由 "弱引用字典"
433 /// 类使用, "弱引用
434 /// 字典" 类使用该类
435 /// 的类型对象来创建
436 /// 表示空引用的 "弱
437 /// 引用" 对象。
438 /// </summary>
439 private class NullObject
440 {}
441 #endregion
442
443 }
444}