這是我的兩分錢,雖然我同意jgauffin和Daniel Hilgarth的答案。以這種方式使用靜態成員使用泛型類型緩存可以直觀地爲每個被緩存的類型創建附加的並行類型,但瞭解這對於引用類型和值類型的工作方式是非常重要的。對於T的引用類型,生成的其他通用類型應使用比等值使用值類型更少的資源。
因此,何時應該使用泛型類型技術來生成緩存?以下是我使用的一些重要標準。 1.您想允許緩存每個感興趣的類的單個實例。 2.您希望使用編譯時通用類型約束來強制緩存中使用的類型的規則。通過類型約束,您可以強制實例需要實現多個接口,而無需爲這些類定義基本類型。 3.您無需在AppDomain的生命週期中從緩存中移除項目。
順便說一句可能對搜索有用的術語是「代碼爆炸」,它是一個通用術語,用於定義需要大量代碼執行一些常規任務並且通常線性增長或隨着項目需求的增長而變得更糟。就通用類型而言,我已經聽到並將通常使用術語「類型爆炸」來描述類型的擴散,因爲在開始組合和組合幾種通用類型時。
另一個重要的一點是,在這些情況下,工廠和高速緩存總是可以分開的,並且在大多數情況下,它們可以被賦予一個相同的接口,從而允許用工廠(每次調用的新實例)或緩存本質上包裝工廠和委託通過相同的接口,如果你想使用一個或另一個取決於類型爆炸問題的事情。您的緩存也可以承擔更多的責任,比如更復雜的緩存策略,其中可能會緩存特定類型(例如引用類型與值類型)。如果你對此感到好奇,那就是定義你的泛型類,它在爲你的工廠實現接口的實際具體類型內進行緩存作爲私有類。如果你願意,我可以舉個例子。
更新與示例代碼的要求:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace CacheAndFactory
{
class Program
{
private static int _iterations = 1000;
static void Main(string[] args)
{
var factory = new ServiceFactory();
// Exercise the factory which implements IServiceSource
AccessAbcTwoTimesEach(factory);
// Exercise the generics cache which also implements IServiceSource
var cache1 = new GenericTypeServiceCache(factory);
AccessAbcTwoTimesEach(cache1);
// Exercise the collection based cache which also implements IServiceSource
var cache2 = new CollectionBasedServiceCache(factory);
AccessAbcTwoTimesEach(cache2);
Console.WriteLine("Press any key to continue");
Console.ReadKey();
}
public static void AccessAbcTwoTimesEach(IServiceSource source)
{
Console.WriteLine("Excercise " + source.GetType().Name);
Console.WriteLine("1st pass - Get an instance of A, B, and C through the source and access the DoSomething for each.");
source.GetService<A>().DoSomething();
source.GetService<B>().DoSomething();
source.GetService<C>().DoSomething();
Console.WriteLine();
Console.WriteLine("2nd pass - Get an instance of A, B, and C through the source and access the DoSomething for each.");
source.GetService<A>().DoSomething();
source.GetService<B>().DoSomething();
source.GetService<C>().DoSomething();
Console.WriteLine();
var clock = Stopwatch.StartNew();
for (int i = 0; i < _iterations; i++)
{
source.GetService<A>();
source.GetService<B>();
source.GetService<C>();
}
clock.Stop();
Console.WriteLine("Accessed A, B, and C " + _iterations + " times each in " + clock.ElapsedMilliseconds + "ms through " + source.GetType().Name + ".");
Console.WriteLine();
Console.WriteLine();
}
}
public interface IService
{
}
class A : IService
{
public void DoSomething() { Console.WriteLine("A.DoSomething(), HashCode: " + this.GetHashCode()); }
}
class B : IService
{
public void DoSomething() { Console.WriteLine("B.DoSomething(), HashCode: " + this.GetHashCode()); }
}
class C : IService
{
public void DoSomething() { Console.WriteLine("C.DoSomething(), HashCode: " + this.GetHashCode()); }
}
public interface IServiceSource
{
T GetService<T>()
where T : IService, new();
}
public class ServiceFactory : IServiceSource
{
public T GetService<T>()
where T : IService, new()
{
// I'm using Activator here just as an example
return Activator.CreateInstance<T>();
}
}
public class GenericTypeServiceCache : IServiceSource
{
IServiceSource _source;
public GenericTypeServiceCache(IServiceSource source)
{
_source = source;
}
public T GetService<T>()
where T : IService, new()
{
var serviceInstance = GenericCache<T>.Instance;
if (serviceInstance == null)
{
serviceInstance = _source.GetService<T>();
GenericCache<T>.Instance = serviceInstance;
}
return serviceInstance;
}
// NOTE: This technique will cause all service instances cached here
// to be shared amongst all instances of GenericTypeServiceCache which
// may not be desireable in all applications while in others it may
// be a performance enhancement.
private class GenericCache<T>
{
public static T Instance;
}
}
public class CollectionBasedServiceCache : IServiceSource
{
private Dictionary<Type, IService> _serviceDictionary;
IServiceSource _source;
public CollectionBasedServiceCache(IServiceSource source)
{
_serviceDictionary = new Dictionary<Type, IService>();
_source = source;
}
public T GetService<T>()
where T : IService, new()
{
IService serviceInstance;
if (!_serviceDictionary.TryGetValue(typeof(T), out serviceInstance))
{
serviceInstance = _source.GetService<T>();
_serviceDictionary.Add(typeof(T), serviceInstance);
}
return (T)serviceInstance;
}
private class GenericCache<T>
{
public static T Instance;
}
}
}
基本上總結,上面的代碼是具有界面的概念是提供一種用於服務源的抽象一個控制檯應用程序。我使用了一個IService通用約束來展示它如何可能很重要的一個例子。我不想輸入或發佈1000個單獨的類型定義,因此我做了下一個最好的事情,並創建了三個類 - A,B和C - 並使用每種技術每訪問1000次 - 重複實例化,泛型類型緩存和基於集合的緩存。
對於一小組訪問,差異可以忽略不計,但我的服務構造函數當然是簡單的(默認無參數構造函數),所以它不計算任何內容,訪問數據庫,訪問配置或任何典型服務類當他們被修建時。如果情況並非如此,那麼緩存策略的好處顯然會對性能有利。另外,即使訪問存在1,000,000次訪問的caes中的默認構造函數,仍然存在非高速緩存和高速緩存(3s:120ms)之間的巨大差異,因此,如果您正在進行大容量訪問或需要頻繁訪問的複雜計算通過工廠,緩存將不僅是有益的,而且取決於它是否會影響用戶的感知或時間敏感的業務流程,否則這些好處可以忽略不計。需要記住的重要一點是,不僅僅是實例化時間,您不得不擔心垃圾收集器的負載。
感謝您的回覆,分享您的意見。順便說一下,緩存是由CLR('private T')自動執行的。所以我正在尋找一種方法來衡量緩存查找時間+緩存大小與實例化時間的某種方式。只是有一個觀點的材料證明。 – abatishchev 2011-05-31 12:38:15
取決於容器。如果您只想獲取第一個緩存項目,則查找時間爲O(1)。 – jgauffin 2011-05-31 12:47:38
我沒有容器,它在幕後執行,由CLR執行。它使用什麼 - 我無法想象。不過,這很有趣。 – abatishchev 2011-05-31 12:54:33