{"id":154,"date":"2015-10-18T18:09:22","date_gmt":"2015-10-18T16:09:22","guid":{"rendered":"http:\/\/blog.rollnut.com\/?p=154"},"modified":"2021-04-11T09:21:55","modified_gmt":"2021-04-11T07:21:55","slug":"cachecontainer","status":"publish","type":"post","link":"http:\/\/blog.rollnut.com\/cachecontainer\/","title":{"rendered":"CacheContainer"},"content":{"rendered":"

Stellt das Grundger\u00fcst f\u00fcr einfaches Caching als abstrakte Basisklasse bereit.
\nMit der generischen CacheContainer-Klasse kann kurzerhand mit einem Delegate ein fertiger Cache verwendet werden.<\/p>\n

<\/p>\n

Das Problem:<\/h1>\n

Sellt euch vor Ihr ben\u00f6tigt zur Laufzeit die Datei \u201aExampleImage.png\u2018 welche sich auf einem Netzlaufwerk befindet. Je nach Qualit\u00e4t der Verbindung geht der Zugriff mal schneller oder langsamer. Sagen wir mal im Schnitt 1 Sekunde. Wenn Ihr nun auf dieselbe Datei mehrmals, z.b. 10 mal, zugreift sind das schon 10 Sekunden die der User warten muss.<\/p>\n

Beispiel:<\/strong>
\nIn meinem Beispiel simuliere ich die 1 Sekunde Wartezeit indem der Thread einfach 1 Sekunde wartet. Au\u00dferdem wird f\u00fcr Analysezwecke noch die Dauer aufgezeichnet.<\/p>\n

static void Main(string[] args)\n{\n    const string path = @\"D:\\\\ExampleImage.png\";\n    var startDate = DateTime.Now;\n\n    for (int i = 0; i < 10; i++)\n    {\n        byte[] imageAsBytes = null;\n\n        using (var memStream = new MemoryStream())\n        using (var stream = File.OpenRead(path))\n        {\n            stream.CopyTo(memStream);\n            \/\/ Code above will take 1 second.\n            Thread.Sleep(1000);\n\n            imageAsBytes = memStream.ToArray();\n        }\n\n        \/\/ Do something amazing with the image!\n        Console.WriteLine(string.Format(\"#{0}: {1}\", i, imageAsBytes.Count()));\n    }\n\n    Console.WriteLine(string.Format(\"Finished after {0} sec.\", (DateTime.Now - startDate).Seconds));\n    Console.ReadLine();\n}<\/pre>\n

Hier bietet sich Caching an. Das bedeutet das die Datei einmalig geladen und direkt zwischengespeichert wird um bei den n\u00e4chsten Zugriffen einen erheblichen Geschwindigkeitsvorteil zu haben.<\/p>\n

Ist-Situation<\/h1>\n

Wenn einfaches Caching in Dot.Net gebraucht wird wird dies h\u00e4ufig mit einem Dictionary realisiert. Daf\u00fcr wird einfach vor dem \u00f6ffnen des Streams gepr\u00fcft ob das Bild bereits im Cache enthalten ist (als Key). Sollte dies nicht der Fall sein wird das Bild nach dem Laden zu dem Cache hinzugef\u00fcgt (wodurch der Key bei der n\u00e4chsten Pr\u00fcfung gefunden wird und ein Zugriff auf das Netzlaufwerk nicht mehr n\u00f6tig ist).<\/p>\n

private static readonly Dictionary<string, byte[]> _cache = new Dictionary<string, byte[]>();\n\nstatic void Main(string[] args)\n{\n    const string path = @\"D:\\\\Floor 1.jpeg\";\n    var startDate = DateTime.Now;\n\n    for (int i = 0; i < 10; i++)\n    {\n        byte[] imageAsBytes = null;\n        \/\/ !!! Check first if wanted image is already in cache !!!\n        if (!_cache.TryGetValue(path, out imageAsBytes))\n        {\n            using (var memStream = new MemoryStream())\n            using (var stream = File.OpenRead(path))\n            {\n                stream.CopyTo(memStream);\n                \/\/ Code above will take 1 second.\n                Thread.Sleep(1000);\n\n                imageAsBytes = memStream.ToArray();\n            }\n\n            \/\/ !!! Very important to store the new value in cache !!!\n            _cache.Add(path, imageAsBytes);\n        }\n\n        \/\/ Do something amazing with the image!\n        Console.WriteLine(string.Format(\"#{0}: {1}\", i, imageAsBytes.Count()));\n    }\n\n    Console.WriteLine(string.Format(\"Finished after {0} sec.\", (DateTime.Now - startDate).Seconds));\n    Console.ReadLine();\n}<\/pre>\n

Mit dieser einfachen \u00c4nderung wurde ganze 9 Sekunden eingespart.<\/p>\n

Wenn der Cache ordentlich implementiert wurde dann erfolgt der Zugriff au\u00dfschlie\u00dflich \u00fcber Zugriffsmethoden und nicht inline (da caching h\u00e4ufig im gr\u00f6\u00dferen Rahmen Verwendung findet als nur innerhalb einer Methode). Au\u00dferdem wird so die Integrit\u00e4t des Caches gew\u00e4hrleistet.<\/p>\n

(in unterem Beispiel wurde die Caching- und Lade-Logik in die Methode 'GetImageFromCache' ausgelagert.)<\/em><\/p>\n

private static readonly Dictionary<string, byte[]> _cache = new Dictionary<string, byte[]>();\n\npublic static byte[] GetImageFromCache(string imagePath)\n{\n    if (String.IsNullOrEmpty(imagePath)) throw new ArgumentException(\"imagePath\");\n\n    byte[] imageAsBytes;\n    \/\/ !!! Check first if wanted image is already in cache !!!\n    if (!_cache.TryGetValue(imagePath, out imageAsBytes))\n    {\n        using (var memStream = new MemoryStream())\n        using (var stream = File.OpenRead(imagePath))\n        {\n            stream.CopyTo(memStream);\n            \/\/ Code above will take 1 second.\n            Thread.Sleep(1000);\n\n            imageAsBytes = memStream.ToArray();\n\n            \/\/ !!! Very important to store the new value in cache !!!\n            _cache.Add(imagePath, imageAsBytes);\n        }\n    }\n    return imageAsBytes;\n}<\/pre>\n

In der Main-Methode wird nun einfach die eben erstellte Methode aufgerufen (was dort nebenbei die Lesbarkeit des Codes erh\u00f6ht).<\/p>\n

static void Main(string[] args)\n{\n    const string path = @\"D:\\\\Floor 1.jpeg\";\n    var startDate = DateTime.Now;\n\n    for (int i = 0; i < 10; i++)\n    {\n        byte[] imageAsBytes = GetImageFromCache(path);\n\n        \/\/ Do something amazing with the image!\n        Console.WriteLine(string.Format(\"#{0}: {1}\", i, imageAsBytes.Count()));\n    }\n\n    Console.WriteLine(string.Format(\"Finished after {0} sec.\", (DateTime.Now - startDate).Seconds));\n    Console.ReadLine();\n}<\/pre>\n

Das ist jetzt nur ein kompaktes Beispiel im kleinen Rahmen. Derartige Caches habe ich bereits sehr oft implementiert und immer separat (was Redunanz zur Folge hatte). Manche Caches mussten dann auch ThreadSafe sein und andere sollten sich selbst bereinigen wenn ein gecachtes Objekt ung\u00fcltig oder veraltet ist. Au\u00dferdem besteht bei obigen Code immer die Gefahr das ein unwissender Kollege das originalle Dictionary direkt manipuliert und mir somit meine Integrit\u00e4t versaut.<\/p>\n

Soll-Situation<\/h1>\n

Als L\u00f6sung habe ich das Caching-Verhalten, zusammen mit dem Dictionary, als Klasse gekapselt.
\nBevor ich die Details dieser Klasse erkl\u00e4re m\u00f6chte ich vorerst das Ergebnis beim Verwenden dieser demonstrieren.<\/p>\n

public static CacheContainer<string, byte[]> ImageCache { get; private set; }\n\nstatic void Main(string[] args)\n{\n    const string path = @\"D:\\\\Floor 1.jpeg\";\n    var startDate = DateTime.Now;\n\n    \/\/ Initialize the Cache.\n    ImageCache = new CacheContainer<string, byte[]>((key) =>\n    {\n        if (String.IsNullOrEmpty(key)) return null;\n\n        byte[] imageAsBytes = null;\n\n        using (var memStream = new MemoryStream())\n        using (var stream = File.OpenRead(key))\n        {\n            stream.CopyTo(memStream);\n            \/\/ Code above will take 1 second.\n            Thread.Sleep(1000);\n\n            imageAsBytes = memStream.ToArray();\n        }\n        return new CacheCallbackResult<byte[]>(true, imageAsBytes);\n    });\n\n    for (int i = 0; i < 10; i++)\n    {\n        \/\/ Get image from cache or load it from disc.\n        var imageAsBytes = ImageCache.GetValue(path);\n\n        \/\/ Do something amazing with the image!\n        Console.WriteLine(string.Format(\"#{0}: {1}\", i, imageAsBytes.Count()));\n    }\n\n    Console.WriteLine(string.Format(\"Finished after {0} sec.\", (DateTime.Now - startDate).Seconds));\n    Console.ReadLine();\n}<\/pre>\n

Das private Dictionary-Cache-Feld musste nun einem Property weichen welcher die neue Cache-Klasse bereitstellt. Das Property wird direkt nach betreten der Main-Methode initialisiert. Der Konstruktor erwartet dabei ein Delegate welches einen Key als Parameter und als Result ein Object vom Typ CacheCallbackResult erwartet (dazu sp\u00e4ter mehr). Kurz erkl\u00e4rt passiert in dem oben erstellten Delegate das gleiche wie in der Methode 'GetImageFromCache', aber mit dem Unterschied das die Pr\u00fcfung des Methodenvertrags keinen Fehler wirft sondern stattdessen null zur\u00fcck gibt.<\/p>\n

Anschlie\u00dfend wird die Schleife 10mal aufgerufen welche die Methode \u201aGetValue\u2018 von dem Cache-Objekt aufruft.<\/p>\n

Von der Code-Menge gesehen hat sich wenig ge\u00e4ndert, aber unter der Haube hat sich sehr viel getan.
\nDie Zugriffe gew\u00e4hrleisten nun immer das die Integrit\u00e4t erhalten bleibt, ThreadSafe und zus\u00e4tzliche Methoden mit denen der Cache wieder geleert werden kann.<\/p>\n

Die Basis-Klasse<\/h2>\n

Die Klasse wurde auf zwei Ebenen aufgeteilt. Die Parent-Klasse ist abstrakt und arbeitet ohne Delegate. Sie gew\u00e4hrleistet die Stabilit\u00e4t des Caches f\u00fcr alle erbenden Klassen.<\/p>\n

public abstract class CacheContainerBase<T1, T2>\n{\n    private readonly object _sync = new object();\n    private readonly Dictionary<T1, T2> _cacheDict = new Dictionary<T1, T2>();\n\n    \/\/ .:: Methods - Essential\n\n    public T2 GetValue(T1 key)\n    {\n        T2 value;\n        if (!TryGetValue(key, out value))\n        {\n            throw new KeyNotFoundException(string.Format(\"The given key ({0}) is not supported to get a value.\", (object)key ?? \"NULL\"));\n        }\n        return value;\n    }\n\n    public bool TryGetValue(T1 key, out T2 value)\n    {\n        bool hasGotValue = false;\n        lock (_sync)\n        {\n            \/\/ Check if key exists in cache.\n            hasGotValue = _cacheDict.TryGetValue(key, out value);\n            if (!hasGotValue)\n            {\n                \/\/ Then get the value from abstract method which an inherited class ensures to return something.\n                hasGotValue = TryGetNonCachedValue(key, out value);\n\n                \/\/ Cache the result.\n                if (hasGotValue)\n                {\n                    _cacheDict.Add(key, value);\n                }\n            }\n        }\n        return hasGotValue;\n    }\n\n    protected abstract bool TryGetNonCachedValue(T1 key, out T2 value);\n}<\/pre>\n

Zwei generische Klassenparameter werden vorausgesetzt (Key und Value).
\nDas private Feld \u201a_sync\u2018 <\/strong>soll die Klasse ThreadSafe machen und \u201a_cacheDict\u2018<\/strong> ist das Kernelement der ganzen Klasse und sollte aus dem Beispiel der Ist-Situation noch bekannt sein.<\/p>\n

Die Methode \u201aGetValue\u2018<\/strong> versucht einen Wert anhand des \u00fcbergebenen Keys zu finden indem sie die Methode \u201aTryGetValue\u2018<\/strong> aufruft. Sollte der Key nicht erlaubt sein fliegt eine \u201aKeyNotFoundException\u2018<\/strong>-Exception.<\/p>\n

\u201aTryGetValue\u2018<\/strong> gibt ebenfalls einen Wert anhand des Keys zur\u00fcck (\u00fcber \u201aout\u2018<\/strong> Parameter) wirft aber keine Exception<\/strong> sondern gibt stattdessen false<\/strong> zur\u00fcck.<\/p>\n

Um einen Wert zu finden wird zuerst im Cache nachgesehen ob er bereits existiert. Wenn nicht wird die abstrakte Method \u201aTryGetNonCachedValue\u2018<\/strong> aufgerufen.<\/p>\n

Um einer erbenden Klasse mehr Kontrolle \u00fcber den Cache zu geben ohne das dieser gef\u00e4hrdet wird stellen wir weitere Methoden zu Manipulation bereit.<\/p>\n

\/\/ .:: Methods - Clear Cache\n\nprotected void ClearCache()\n{\n    lock (_sync)\n        _cacheDict.Clear();\n}\nprotected void ClearCache(IEnumerable keys)\n{\n    if (keys == null) throw new ArgumentNullException(\"keys\");\n\n    lock (_sync)\n    {\n        foreach (var key in keys)\n        {\n            _cacheDict.Remove(key);\n        }\n    }\n}<\/pre>\n

Die \u00fcberladene \u201aClearCache\u2018<\/strong>-Methode macht das was Sie sagt. Entweder werden alle Eintr\u00e4ge aus dem Cache entfernt oder nur f\u00fcr bestimmte Keys.<\/p>\n

\/\/ .:: Methods - Cache Contains Key\/Value\n\nprotected bool CacheContainsKey(T1 key)\n{\n    lock (_sync)\n        return _cacheDict.ContainsKey(key);\n}\nprotected bool CacheContainsValue(T2 value)\n{\n    lock (_sync)\n        return _cacheDict.ContainsValue(value);\n}<\/pre>\n

\u201aCacheContainsKey\u2018<\/strong> oder \u201aCacheContainsValue\u2018<\/strong> geben je true zur\u00fcck bei passender \u00dcbereinstimmung.<\/p>\n

Zu erw\u00e4hnen ist das alle vier Methoden das selbe Objekt f\u00fcr lock<\/strong>\u00a0verwenden welches auch die Methode \u201aTryGetValue\u2018<\/strong> verwendet.\u00a0Das f\u00fchrt dazu das jeder asynchrone Zugriff \u00fcber alle Methoden hinweg synchronisiert wird und damit die Integrit\u00e4t des Caches auch bei asynchroner Verwendung sichergestellt ist.<\/p>\n

Au\u00dferdem sind die vier Methoden als protected markiert um einer m\u00f6glichen Child-Klasse Kontrolle \u00fcber das Objekt zu geben ohne dabei nach au\u00dfen Funktionalit\u00e4t zu \u00f6ffnen (ein praktisches Anwendungsbeispiel wird sp\u00e4ter erl\u00e4utert).<\/p>\n

Cache-Klasse mithilfe von Delegate<\/h2>\n
public class CacheContainer<T1, T2> : CacheContainerBase<T1, T2>\n{\n    private readonly Func<T1, CacheCallbackResult> _tryGetNonCachedValueFunc;\n\n    public CacheContainer(Func<T1, CacheCallbackResult> tryGetNonCachedValueFunc)\n    {\n        if (tryGetNonCachedValueFunc == null)\n            throw new ArgumentNullException(\"tryGetNonCachedValueFunc\");\n        _tryGetNonCachedValueFunc = tryGetNonCachedValueFunc;\n    }\n\n    \/\/ .:: Methods - Essential\n\n    protected override bool TryGetNonCachedValue(T1 key, out T2 value)\n    {\n        var result = _tryGetNonCachedValueFunc.Invoke(key);\n        value = result == null\n            ? default(T2)\n            : result.Value;\n        return result == null\n            ? false\n            : result.KeyIsValid;\n    }\n}<\/pre>\n

Die Child-Klasse \u00fcberschreibt die abstrakte Methode vom Parent (TryGetNonCachedValue<\/strong>). Dort wird lediglich ein Delegate aufgerufen welches im Konstruktor der Klasse zwingend erforderlich ist.<\/p>\n

Das Delegate muss als Parameter den Key (T1) entgegennehmen und den Value gekapselt in der Klasse CacheCallbackResult<\/strong> zur\u00fcckgeben (dazu sp\u00e4ter mehr). Is das Result NULL<\/strong> oder aber dessen Property \u201aIsKeyValid\u2018<\/strong> false ist zu erwarten das der Key im Parameter nicht g\u00fcltig ist und es kann false zur\u00fcckgegeben werden.<\/p>\n

\/\/ .:: Methods - Protected to Public\n\npublic new void ClearCache()\n{\n    base.ClearCache();\n}\npublic new void ClearCache(IEnumerable keys)\n{\n    base.ClearCache(keys);\n}\n\npublic new bool CacheContainsKey(T1 key)\n{\n    return base.CacheContainsKey(key);\n}\npublic new bool CacheContainsValue(T2 value)\n{\n    return base.CacheContainsValue(value);\n}<\/pre>\n

Da die Child-Klasse nach au\u00dfen recht geschlo\u00dfen ist empfehle ich die vier Methoden vom Parent welche protected sind nach au\u00dfen freizugeben. Dies ist enorm hilfreich f\u00fcr Debugging-Zwecke.<\/p>\n

\u00dcbrigens wird in der Child-Klasse komplett auf das \u201alock\u2018<\/strong>-Schl\u00fc\u00dfelwort verzichten da der Parent sich bereits darum k\u00fcmmert.<\/p>\n

CacheCallbackResult<\/h2>\n

Welcher\u00a0Delegatetyp eignet sich am besten wenn man eine Methoden-Signatur ben\u00f6tigt welche einen Parameter und zwei R\u00fcckgabewerte verlangt? Bei einer normalen Methode w\u00fcrde man wohl einfach einen out Parameter verwenden was folgender Delegate-Signatur entspricht.<\/p>\n

delegate bool TryGetNonCachedValueDelegate(T1 key, out T2 value);<\/p>\n

Leider unterst\u00fctzen Inline-Delegates keine 'out'<\/strong>-Parameter. Es w\u00e4hre sehr schade wenn der CacheContainer keine Inline-Schreibweise unterst\u00fctzden w\u00fcrde. Daher bin ich zum Entschluss gekommen das man beide Results einfach in folgender Wrapper-Klasse zur\u00fcckgibt und auf das 'out'<\/strong> verzichtet.<\/p>\n

public class CacheCallbackResult\n{\n    public CacheCallbackResult(bool keyIsValid, T value)\n    {\n        this.KeyIsValid = keyIsValid;\n        this.Value = value;\n    }\n    public bool KeyIsValid { get; private set; }\n    public T Value { get; private set; }\n}<\/pre>\n

Der erste Parameter 'keyIsValid'<\/strong>\u00a0enth\u00e4lt die\u00a0Information ob der Key eine g\u00fcltige Struktur aufweist (ung\u00fcltige Keys geben nat\u00fcrlich keinen Wert zur\u00fcck\u00a0und werden\u00a0nicht im Cache gehalten).
\nAls zweiten, generischen, Parameter 'value'<\/strong>\u00a0wird der Value erwartet welcher zu dem Key gefunden wurde.<\/p>\n

Der komplette Code<\/h1>\n

Den kompletten Code k\u00f6nnt Ihr unten (mit Kommentaren) finden. Ich habe dort noch zwei Erg\u00e4nzung in der Klasse \u201aCacheContainerBase\u2018<\/strong> gemacht welche ebenfalls hilfreich sind.<\/p>\n

1. Ein zweiter Cache welcher sich alle Invaliden Keys merkt.
\n2. Ein Property \u201aCanCacheInvalidKeys\u2018 welches bei false obiges Verhalten unterdr\u00fcckt.<\/p>\n

CacheContainerBase<\/h2>\n
\/\/\/ <summary>\n\/\/\/ Provide basic caching by key in a thread safe way.\n\/\/\/ <\/summary>\n\/\/\/ <typeparam name=\"T1\">The key type.<\/typeparam>\n\/\/\/ <typeparam name=\"T2\">The value type.<\/typeparam>\npublic abstract class CacheContainerBase<T1, T2>\n{\n    \/\/ .:: Fields\n\n    \/\/\/ <summary>\n    \/\/\/ Sync all method calls to avoid async problems.\n    \/\/\/ <\/summary>\n    private readonly object _sync = new object();\n\n    \/\/\/ <summary>\n    \/\/\/ The cache for valid keys.\n    \/\/\/ <\/summary>\n    private readonly Dictionary<T1, T2> _cacheDict = new Dictionary<T1, T2>();\n\n    \/\/ The hashset is the better collection for the job of caching invalid keys, but not available in pcl.\n    \/\/private readonly HashSet<T1> _cache_InvalidKeys = new HashSet<T1>();\n    \/\/\/ <summary>\n    \/\/\/ The cache for invalid keys.\n    \/\/\/ <\/summary>\n    private readonly Dictionary<T1, byte> _cache_InvalidKeys = new Dictionary<T1, byte>();\n\n\n    \/\/ .:: Constructor\n\n    public CacheContainer()\n    {\n        this.CanCacheInvalidKeys = true;\n    }\n\n\n    \/\/ .:: Properties\n\n    \/\/\/ <summary>\n    \/\/\/ Set to false in order to avoid caching for keys which are not valid to request.\n    \/\/\/ <\/summary>\n    protected bool CanCacheInvalidKeys { get; set; }\n\n\n    \/\/ .:: Methods - Essential\n\n    \/\/\/ <summary>\n    \/\/\/ Get the value which belongs to given key.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"key\">The expected value belongs to this key.<\/param>\n    \/\/\/ <exception cref=\"KeyNotFoundException\">The key is not valid for this cache.<\/exception>\n    public T2 GetValue(T1 key)\n    {\n        T2 value;\n        if (!TryGetValue(key, out value))\n        {\n            throw new KeyNotFoundException(string.Format(\"The given key ({0}) is not supported to get a value.\", (object)key ?? \"NULL\"));\n        }\n        return value;\n    }\n    \/\/\/ <summary>\n    \/\/\/ Get the value which belongs to given key.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"key\">The expected value belongs to this key.<\/param>\n    \/\/\/ <param name=\"value\">The expected value.<\/param>\n    \/\/\/ <returns>Returns true if key is valid. False if key is invalid to this cache.<\/returns>\n    public bool TryGetValue(T1 key, out T2 value)\n    {\n        bool hasGotValue = false;\n        lock (_sync)\n        {\n            \/\/ 1. Check if key exists in cache.\n            hasGotValue = _cacheDict.TryGetValue(key, out value);\n            if (!hasGotValue)\n            {\n                \/\/ 2. Check if key exists in invalid key cache.\n                bool isKeyInvalid = this.CanCacheInvalidKeys\n                    ? _cache_InvalidKeys.ContainsKey(key)\n                    : false;\n                if (isKeyInvalid)\n                {\n                    hasGotValue = false;\n                    value = default(T2);\n                }\n                else\n                {\n                    \/\/ 3. Then get the value from abstract method which an inherited class ensures to return something.\n                    hasGotValue = TryGetNonCachedValue(key, out value);\n\n                    \/\/ 3.1 Cache the result.\n                    if (hasGotValue)\n                    {\n                        _cacheDict.Add(key, value);\n                    }\n                    \/\/ 3.2 Cache the result in invalid keys.\n                    else if (this.CanCacheInvalidKeys)\n                    {\n                        _cache_InvalidKeys.Add(key, byte.MinValue);\n                    }\n                }\n            }\n        }\n        return hasGotValue;\n    }\n\n\n    \/\/\/ <summary>\n    \/\/\/ This method is only called if given key was not found in cache.\n    \/\/\/ The abstract method should return a value (out parameter) which belongs to given key \n    \/\/\/ and a bool which is true when given key was valid.\n    \/\/\/ The returned value will be cached then.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"key\">The expected value belongs to this key.<\/param>\n    protected abstract bool TryGetNonCachedValue(T1 key, out T2 value);\n\n\n    \/\/ .:: Methods - Clear Cache\n\n    \/\/\/ <summary>\n    \/\/\/ Clear all cached entries.\n    \/\/\/ <\/summary>\n    \/\/\/ <remarks> Clear in normal cache and invalid key cache.<\/remarks>\n    protected void ClearCache()\n    {\n        lock (_sync)\n        {\n            _cacheDict.Clear();\n            _cache_InvalidKeys.Clear();\n        }\n    }\n    \/\/\/ <summary>\n    \/\/\/ Clear only cached entries which depends to given keys.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"keys\">Remove only this keys from cache.<\/param>\n    \/\/\/ <remarks> Clear in normal cache and invalid key cache.<\/remarks>\n    protected void ClearCache(IEnumerable<T1> keys)\n    {\n        if (keys == null) throw new ArgumentNullException(\"keys\");\n\n        lock (_sync)\n        {\n            foreach (var key in keys)\n            {\n                \/\/ 1. Remove from cache.\n                if (!_cacheDict.Remove(key))\n                {\n                    \/\/ 2. Remove from invalid key cache.\n                    _cache_InvalidKeys.Remove(key);\n                }\n            }\n        }\n    }\n\n\n    \/\/ .:: Methods - Cache Contains Key\/Value\n\n    \/\/\/ <summary>\n    \/\/\/ Check if given key is present in cache.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"key\">The key which is present in cache.<\/param>\n    \/\/\/ <remarks> Check in normal cache and invalid key cache.<\/remarks>\n    protected bool CacheContainsKey(T1 key)\n    {\n        lock (_sync)\n        {\n            return _cacheDict.ContainsKey(key)\n                ? true\n                : _cache_InvalidKeys.ContainsKey(key);\n        }\n    }\n    \/\/\/ <summary>\n    \/\/\/ Check if given value is present in cache.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"value\">The value which is present in cache.<\/param>\n    protected bool CacheContainsValue(T2 value)\n    {\n        lock (_sync)\n            return _cacheDict.ContainsValue(value);\n    }\n}<\/pre>\n

CacheContainer<\/h2>\n
\/\/\/ <summary>\n\/\/\/ Provide basic caching by key.\n\/\/\/ Get value is supported by a delegate from outside (no inheritance is required).\n\/\/\/ <\/summary>\n\/\/\/ <typeparam name=\"T1\">The key type.<\/typeparam>\n\/\/\/ <typeparam name=\"T2\">The value type.<\/typeparam>\n\/\/\/ <remarks>\n\/\/\/ Hinweis auf delegate und anwendungsbeispiel\n\/\/\/ und warum alle protected methoden public wurden\n\/\/\/ <\/remarks>\npublic class CacheContainer<T1, T2> : CacheContainerBase<T1, T2>\n{\n    \/\/\/ <summary>\n    \/\/\/ Delegate which return a value for given key.\n    \/\/\/ <\/summary>\n    private readonly Func<T1, CacheCallbackResult<T2>> _tryGetNonCachedValueFunc;\n\n    \/\/\/ <summary>\n    \/\/\/ Cache by key. Value is provided by given delegate.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"tryGetNonCachedValueFunc\">The deleagte which returns a value for given key as \n    \/\/\/ the 'CacheCallbackResult'-object (or the flase 'KeyIsValid'-flag).<\/param>\n    public CacheContainer(Func<T1, CacheCallbackResult<T2>> tryGetNonCachedValueFunc)\n    {\n        if (tryGetNonCachedValueFunc == null) throw new ArgumentNullException(\"tryGetNonCachedValueFunc\");\n        _tryGetNonCachedValueFunc = tryGetNonCachedValueFunc;\n    }\n\n    \/\/ .:: Methods - Essential\n\n    \/\/\/ <summary>\n    \/\/\/ Overridden method which returns the value from key by invoke given delegate.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"key\">The key to get the value.<\/param>\n    \/\/\/ <param name=\"value\">The value which is excpected by given key.<\/param>\n    \/\/\/ <returns>False if given key is not supported.<\/returns>\n    protected override bool TryGetNonCachedValue(T1 key, out T2 value)\n    {\n        var result = _tryGetNonCachedValueFunc.Invoke(key);\n        value = result == null\n            ? default(T2)\n            : result.Value;\n        return result == null\n            ? false\n            : result.KeyIsValid;\n    }\n\n    \/\/ .:: Methods - Protected to Public\n\n    \/\/\/ <summary>\n    \/\/\/ Clear all cached entries.\n    \/\/\/ <\/summary>\n    \/\/\/ <remarks> Clear in normal cache and invalid key cache.<\/remarks>\n    public new void ClearCache()\n    {\n        base.ClearCache();\n    }\n    \/\/\/ <summary>\n    \/\/\/ Clear only cached entries which depends to given keys.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"keys\">Remove only this keys from cache.<\/param>\n    \/\/\/ <remarks> Clear in normal cache and invalid key cache.<\/remarks>\n    public new void ClearCache(IEnumerable<T1> keys)\n    {\n        base.ClearCache(keys);\n    }\n\n    \/\/\/ <summary>\n    \/\/\/ Check if given key is present in cache.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"key\">The key which is present in cache.<\/param>\n    public new bool CacheContainsKey(T1 key)\n    {\n        return base.CacheContainsKey(key);\n    }\n    \/\/\/ <summary>\n    \/\/\/ Check if given value is present in cache.\n    \/\/\/ <\/summary>\n    \/\/\/ <param name=\"value\">The value which is present in cache.<\/param>\n    public new bool CacheContainsValue(T2 value)\n    {\n        return base.CacheContainsValue(value);\n    }\n}\n\n\/\/\/ <summary>\n\/\/\/ A helper class for CacheContainer to support a 'TryGetValue'-Behavior.\n\/\/\/ <\/summary>\n\/\/\/ <typeparam name=\"T\"><\/typeparam>\npublic class CacheCallbackResult<T>\n{\n    public CacheCallbackResult(bool keyIsValid, T value)\n    {\n        this.KeyIsValid = keyIsValid;\n        this.Value = value;\n    }\n    public CacheCallbackResult(Exception ex)\n    {\n        this.Exception = ex;\n    }\n    public bool KeyIsValid { get; private set; }\n    public T Value { get; private set; }\n    public Exception Exception { get; set; }\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"

Stellt das Grundger\u00fcst f\u00fcr einfaches Caching als abstrakte Basisklasse bereit. Mit der generischen CacheContainer-Klasse kann kurzerhand mit einem Delegate ein fertiger Cache verwendet werden.<\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[7,8],"tags":[],"_links":{"self":[{"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/posts\/154"}],"collection":[{"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/comments?post=154"}],"version-history":[{"count":18,"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/posts\/154\/revisions"}],"predecessor-version":[{"id":918,"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/posts\/154\/revisions\/918"}],"wp:attachment":[{"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/media?parent=154"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/categories?post=154"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.rollnut.com\/wp-json\/wp\/v2\/tags?post=154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}