设计模式 - 泛型缓存 / 类型字典 / 泛型字典

创建时间:
2018-10-29 11:13
最近更新:
2018-11-05 23:43

命名 - 三种方案

  1. 装配脑袋 命名为 类型字典 (Type Dictionary)。装配脑袋 某篇博文中说 "'类型字典' 这个词是我根据其特性杜撰的"。赵劼 说 "但我觉得 '类型字典' 没有突出 '泛型' 的含义 且 有些 Dictionary<Type> 的感觉"。
  2. 赵劼 命名为 泛型字典/泛型参数字典。装配脑袋 说 "'泛型字典' 显然会被人理解成 Dictionary<T>"。
  3. Visitor.Name 命名为 泛型缓存。他认为 "泛型字典已然成为了 Dictionary 的别称,用这个名字不太好。从功能上定义,'字典' 应当是根据一个索引 (或者键) 查找相应值 (或者值集合) 的对象,而 Cache 则是根据一个索引生成一个值,同时用生成的值构造一个缓存,如果下次索引相同就可以缓存否则继续生成。从功能上来说,Cache 是一个动态生成的固定表,所以直接使用 '泛型缓存/泛型缓存表/泛型对象缓存/泛型实例缓存' 之类的名字即可,没必要纠结于字典二字"。

T: 采用 Visitor.Name 的命名方案 泛型缓存

网摘 (类型字典 by 装配脑袋)

Source: 泛型技巧系列: 类型字典和Type Traits

"类型字典" 这个词是我根据其特性杜撰的,它其实利用到 .NET 泛型很重要的一个特性: 泛型类的静态字段。如果有一个泛型类型 A(Of T),那么它的每一个封闭构造类型 (Closed Constructed Type) 独享一组静态字段的取值。也就是说,假设 XA(Of T) 的静态字段 (VB 中的 Shared 或 C# 中的 static),那么 A(Of Integer).XA(Of String).X 是互相独立的两个字段,改变一个的值不会影响到另一个。这样就给我们提供了一个极为便利的条件,我们可以利用泛型类型的静态字段,为类型参数的每一组封闭取值 (封闭的意思是所有取值为非泛型类型或泛型封闭构造类型) 保存一个字段的值。简单地说,这就相当于以类型作为关键字,建立了一个字典,其值则是一个对象。以下为 C# 语法的一个小小的例子:

class TypeDict<T>
{
    public static int Value;
}

//在代码中,我们可以为每个封闭类型保存一个 int,这相当于一个 类型到int 的字典:
TypeDict<int>.Value = 1;
TypeDict<string>.Value = 2;
TypeDict<List<int>>.Value = 3;
TypeDict<List<double>>.Value = 4;

从类型查询到其相应的值,只有访问一个静态字段的代价,可谓极低。

网摘 (泛型字典 by 赵劼)

Source: 缓存方式与对象创建的性能比较

泛型字典

在很多场景下,我们会为每个类型保存一个对应的对象。如果可以得到泛型参数的话,我们可以使用泛型字典来进行保存:

public static class Cache<T>
{
    public static object Instance { get; set; }
}

而测试代码便是:

private static void InitGenericStorage()
{
    Cache<object>.Instance = null;
    TestGenericStorage(1); // warm up;
}

private static void TestGenericStorage(int iteration)
{
    for (int i = 0; i < iteration; i++) {
        var instance = Cache<object>.Instance;
    }
}

普通字典

但是,很多时候我们无法得到泛型参数信息 (如这里),因此无法使用泛型字典。此时我们只能将对象保存在一个 Dictionary 中,测试代码如下:

private static Dictionary<Type, object> s_normalDict;

private static void InitNormalDictionary()
{
    s_normalDict = new Dictionary<Type, object>();
    s_normalDict[typeof(object)] = new object();

    TestNormalDictionary(1); // warm up
}

private static void TestNormalDictionary(int iteration)
{
    var key = typeof(object);

    for (int i = 0; i < iteration; i++)
    {
        var instance = s_normalDict[key];
    }
}

结论

  • 泛型字典的性能远高于使用普通字典进行存储
  • 直接构造对象的性能不一定会比保存在字典里差

回复评论

qiaojie:
这玩意有什么用?
直接把 Instance 作为具体类的静态属性不就结了么?
搞个泛型类去放 Instance,有多此一举的嫌疑。

赵劼:
这玩意儿可是相当有用。
静态属性没法根据统一的签名进行获取,而 Cache<T> 是可以的。
试想这么一个 API:

TBinder GetBinder<TBinder>()
{
return BinderCache<TBinder>.Instance;
}

那么可以 GetBinder<StringBinder>() 和 GetBinder<ArticleBinder>()。
否则你的 GetBinder 方法拿到个 TBinder 后,又该怎么访问各自独立的静态属性?

Ivony:
这玩意儿等于为所有已知未知的类型全部增加了一个静态属性,不管它愿不愿意。

赵劼:
而且为静态属性统一签名了,可以用一致的方式访问

qiaojie:
除了可以看成是一个非侵入式的 singleton 实现方案,还有没有更有意义的使用场景?

Ivony:
这个与 singleton 没有关系,属性的类型不是 T,而是某个确定的类型,当然也可以是 T。
至于使用场景,这是一个泛型的特性,怎么灵活运用主要还是看程序员自己。并没有什么标准的使用场景。其实可以用到的地方是非常多的。

赵劼:
一个 Cache<T> 类里也可以不止一个字段。
这个 "泛型参数字典" 主要就是可以把一个或多个对象和某个泛型参数对应起来,而且线程安全 (在静态构造函数里初始化),性能高。