C#中的弱引用和强引用

今天在看CommunityToolkit.Mvvm包里的消息这一块时,发现内部实现使用了ConditionalWeakTable<TKey,TValue>这个类,查了一下资料,发现这个类是弱引用 相关的。

这个做个完整的总结,防止以后忘记。

 

强引用(strong reference)

什么叫强引用,简单来说,就是保持对象的可访问性,防止被垃圾回收(GC)。这里指的可访问性,是指该对象正由程序使用中。

 

弱引用(weak reference)

弱引用允许应用程序访问对象,同时也允许垃圾回收相应的对象。 如果不存在强引用,则弱引用的有限期只限于收集对象前的一个不确定的时间段。

简单点说,就是将一个对象声明成弱引用后,下次你去访问时,有可能访问得到(没有被垃圾回收),有可能访问不到(已经被垃圾回收掉了)。

占用大量内存,但通过垃圾回收功能回收以后很容易重新创建的对象特别适合使用弱引用。

 

短弱引用和长弱引用 

短弱引用

垃圾回收功能回收对象后,短弱引用的目标会变为 null。 弱引用本身是托管对象,与其他任何托管对象一样需要经过垃圾回收。 短弱引用是 WeakReference 的无参数构造函数。

 

长弱引用

在对象的 Finalize 方法已调用后,长弱引用获得保留。 这样,便可以重新创建该对象,但该对象仍保持不可预知的状态。 如果要使用长引用,在 WeakReference 构造函数中指定为true

如果对象类型不包含 Finalize 方法,应用的是短弱引用功能。弱引用只在目标被收集前有效,运行终结器后可以随时收集目标。

长引用这里我其实不是很理解 ,直到我看到了这个链接

https://stackoverflow.com/questions/15120942/any-practical-example-of-long-weak-reference

 

WeakReference和WeakReference<T>

弱引用主要通过WeakReferenceWeakReference<T>两种类型实现。两者的区别是WeakReference<T>可以指定类型,WeakReference不能指定类型。

 

下面通过一个示例,来演示一下,如何使用弱使用。

 

首先定义一个数据类(Data),用于存储数据,构造函数可以指定数据大小。

 1 /// <summary>
 2 /// 字节数据类
 3 /// </summary>
 4 public class Data
 5 {
 6     private byte[] data;   //字节数据  这个字段实际没有用到,只是为了模拟大量数据
 7     private string name;   //名称 返回size
 8 
 9     public Data(int size)
10     {
11         data = new byte[size * 1024];
12         name = size.ToString();
13     }
14 
15     public string Name
16     {
17         get { return name; }
18     }
19 }

 

再定义一个缓存类(Cache)

这个缓存类里有一个缓存字典,Key是数字,Value是一个WeakReference对象。

在构造函数里,指定数量,初始化字典,以及创建对应的弱引用。

通过索引器方法来取字典里的值

 1 /// <summary>
 2 /// 缓存类
 3 /// </summary>
 4 public class Cache
 5 {
 6     // 缓存字典
 7     static Dictionary<int, WeakReference> cache;
 8 
 9     // 重新创建的个数
10     int regenCount = 0;
11 
12     public Cache(int count)
13     {
14         cache = new Dictionary<int, WeakReference>();
15 
16         //创建指定数量的弱引用
17         for (int i = 0; i < count; i++)
18         {
19             cache.Add(i, new WeakReference(new Data(i), false));
20         }
21     }
22 
23     /// <summary>
24     /// 缓存数量
25     /// </summary>
26     public int Count
27     {
28         get { return cache.Count; }
29     }
30 
31     /// <summary>
32     /// 需要重新创建的对象个数
33     /// </summary>
34     public int RegenerationCount
35     {
36         get { return regenCount; }
37     }
38 
39     /// <summary>
40     /// 从缓存获取数据
41     /// </summary>
42     /// <param name="index"></param>
43     /// <returns></returns>
44     public Data this[int index]
45     {
46         get
47         {
48             Data d = cache[index].Target as Data;
49             if (d == null)
50             {
51                 // 如果对象已经被回收了,重新创建一个
52                 Console.WriteLine("索引 {0}: 的数据需要重新创建", index);
53                 d = new Data(index);
54                 cache[index].Target = d;
55                 regenCount++;
56             }
57             else
58             {
59                 // 通过弱引用获取对象,不需要重新创建
60                 Console.WriteLine("索引 {0}: 的数据不需要重新创建", index);
61             }
62 
63             return d;
64         }
65     }
66 }

 

测试代码:

创建一个数量为50的缓存,然后循环,随机访问缓存里的数据,

输出缓存里的数据是否需要重新创建,以及重新创建的比例

 1 public class Program
 2 {
 3     public static void Main()
 4     {
 5         //假设缓存数量为50
 6         int cacheSize = 50;
 7 
 8         Random r = new Random();
 9         Cache c = new Cache(cacheSize);
10 
11         string DataName = "";
12         GC.Collect(0);
13 
14         // 随机从缓存中取数据
15         for (int i = 0; i < c.Count; i++)
16         {
17             int index = r.Next(c.Count);
18 
19             // 访问对象的属性值
20             DataName = c[index].Name;
21         }
22         
23         //输出结果
24         double regenPercent = c.RegenerationCount / (double)c.Count;   //对象重新创建的比例
25         Console.WriteLine("缓存数量: {0}, 重新创建的比例: {1:P2}", c.Count, regenPercent);
26     }
27 }

 

运行结果

 

示例代码

 

推荐阅读:

https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/

posted @ 2022-12-05 17:28  zhaotianff  阅读(878)  评论(0编辑  收藏  举报