|
using System;
using System.Collections.Generic;
using System.Threading;
namespace Pfz.Caching
{
/// <summary>
/// This class acts as a cache for function evalutations. This is inspired
/// by the functional and immutable programming model, so calling a function
/// twice with the same parameter, which will generate the same result, will
/// be evaluated only once.
/// As a cache, such generated results can be garbage collected, but the
/// cache is able to generate the same value again, given the same
/// parameter. And, of course, the parameter can be an struct, so this
/// enables multi-parameters with a little extra effort.
///
/// To use this cache, you must override and implement the method Generate.
/// It is common to create the cache as an inheritor of this class with
/// a private constructor, and make it acessible by a static function call.
/// </summary>
public abstract class ResultCache<TParam, TResult>
{
#region Private fields
private ReaderWriterLockSlim fLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private Dictionary<TParam, Item> fDictionary = new Dictionary<TParam, Item>();
#endregion
#region Constructor
/// <summary>
/// Initializes the functional cache, and register it in the Collected event.
/// </summary>
public ResultCache()
{
GCUtils.Collected += p_Collected;
}
#endregion
#region p_Collected
private void p_Collected()
{
GCUtils.Collected += p_Collected;
fLock.EnterWriteLock();
try
{
var oldDictionary = fDictionary;
fDictionary = new Dictionary<TParam, ResultCache<TParam, TResult>.Item>(oldDictionary.Count);
int lastGeneration = GC.CollectionCount(GC.MaxGeneration)-1;
foreach(var pair in oldDictionary)
if (pair.Value.CollectionCount >= lastGeneration)
fDictionary.Add(pair.Key, pair.Value);
}
finally
{
fLock.ExitWriteLock();
}
}
#endregion
#region DoExecute
/// <summary>
/// You must implement this method to effectivelly execute the function.
/// Remember that such function must always return the same value considering
/// the same input values.
/// </summary>
/// <param name="parameter">The parameter value.</param>
/// <returns>Your generated result value.</returns>
protected abstract TResult DoExecute(TParam parameter);
#endregion
#region Execute
/// <summary>
/// Evaluates the function, or uses the cached value if this function
/// was already evaluated with the same parameters.
///
/// This method is virtual so it is possible to clone the result in cases
/// where the result is modifiable just before returning it.
/// As the value is cached, you must guarantee that the value is not modifiable
/// or, if it is, at least clone it so the returned value, if modified, will
/// not destroy the cached result.
/// </summary>
/// <param name="parameter">The parameter value.</param>
/// <returns>Your generated result value.</returns>
public virtual TResult Execute(TParam parameter)
{
Item item;
int collectionCount = GC.CollectionCount(GC.MaxGeneration);
fLock.EnterReadLock();
try
{
if (fDictionary.TryGetValue(parameter, out item))
{
item.CollectionCount = collectionCount;
return item.Result;
}
}
finally
{
fLock.ExitReadLock();
}
fLock.EnterUpgradeableReadLock();
try
{
// as we released the lock, we need to check-again.
if (fDictionary.TryGetValue(parameter, out item))
{
item.CollectionCount = collectionCount;
return item.Result;
}
item = new Item();
item.CollectionCount = collectionCount;
item.Result = DoExecute(parameter);
fLock.EnterWriteLock();
try
{
fDictionary.Add(parameter, item);
return item.Result;
}
finally
{
fLock.ExitWriteLock();
}
}
finally
{
fLock.ExitUpgradeableReadLock();
}
}
#endregion
#region Clear
/// <summary>
/// Clears the cache.
/// </summary>
protected void Clear()
{
fLock.EnterWriteLock();
try
{
fDictionary.Clear();
}
finally
{
fLock.ExitWriteLock();
}
}
#endregion
#region Add
/// <summary>
/// Adds a value to the cache. This is here so inherited classes
/// that know they are generating values valid to be added to the
/// cache add it. Remember that this method throws an exception
/// when the value already exists and that Cache in general
/// must be thread-safe.
/// </summary>
protected void Add(TParam parameter, TResult result)
{
Item item = new Item();
item.CollectionCount = GC.CollectionCount(GC.MaxGeneration);
item.Result = result;
fLock.EnterWriteLock();
try
{
fDictionary.Add(parameter, item);
}
finally
{
fLock.ExitWriteLock();
}
}
#endregion
#region AddOrReplace
/// <summary>
/// Adds or replaces a value from the cache. This method is here
/// so inherited classes can replace existing cached values if,
/// for some reason, such value changes.
/// If you need to do this, remember that the cache can be used
/// by many threads and even the cache itself being thread-safe,
/// you may need your own locking to avoid incoherent states.
/// </summary>
protected void AddOrReplace(TParam parameter, TResult result)
{
Item item = new Item();
item.CollectionCount = GC.CollectionCount(GC.MaxGeneration);
item.Result = result;
fLock.EnterWriteLock();
try
{
fDictionary[parameter] = item;
}
finally
{
fLock.ExitWriteLock();
}
}
#endregion
#region Remove
/// <summary>
/// Removes a cached result.
/// </summary>
protected bool Remove(TParam parameter)
{
fLock.EnterWriteLock();
try
{
return fDictionary.Remove(parameter);
}
finally
{
fLock.ExitWriteLock();
}
}
#endregion
#region Item - Nested class
private sealed class Item
{
internal int CollectionCount;
internal TResult Result;
}
#endregion
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.
At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.
Want more info or simply want to contact me?
Take a look at:
http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com
Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).