Coding – RollNut ~ Blog http://blog.rollnut.com Sun, 11 Apr 2021 07:35:07 +0000 de-DE hourly 1 https://wordpress.org/?v=5.7.11 Git Glossar http://blog.rollnut.com/git-glossar/ Mon, 30 Jan 2017 13:43:20 +0000 http://blog.rollnut.com/?p=748 Hier findet Ihr die wichtigsten Git-Befehle. Selbst wenn Ihr eine grafische Git-Oberfläche verwendet ist es gut zu wissen welche Shell-Befehle es gibt und was diese tun.

Git einrichten
init Neues Repository erstellen (aktuelles Verzeichnis).
init --bare Ein zentrales Repository erstellen.
Achtung! Der Ordnername sollte mit .git enden (z.B. my-project.git).
clone <repo> Kopiert ein Repository welches unter <repo> zu finden ist.
config --global Globale Konfiguration von git (z.B. Benutzerinfo angeben).
Mit Git lokal arbeiten
add <name> Dateien und Ordner zur "Staging Area" hinzufügen (abhängig von <name>).
Mit * werden alle Änderung hinzugefügt (.ignore-Regeln werden aber beachtet).
commit Änderungen in der "Staged Area" werden in das lokale Repository übernommen.
stash Speichert alle lokalen Änderungen in ein lokales Stash-Objekt.
Nützlich um aktuelle Arbeit ohne Commit zu sichern.
status Zeigt offene Änderungen, welche noch nicht committed wuden.
log Zeigt Commits (Ausgabe kann über Parameter eingestellt werden, wie z.B. --graph).
Branching
branch Branches verwalten (erstellen, löschen, umbenennen)
checkout <branch> Von einem Branch in ein anderes wechseln.
Es können auch einzelne Dateien oder Commits ausgecheckt werden.
merge --no-ff <branch> Das aktuelle Branch mit <branch> zusammenführen.
Für Größere merges empfiehlt es sich immer --no-ff zu verwenden (bessere Chronik).
Nach dem Merge muss normal committed werden.
Git synchronisieren (mit zentralem Repository)
remote Remote-Verbindungen verwalten.
Dieser Befehl erstellt quasi ein Alias für umständlich lange URL's.
fetch Git holt sich den aktuellen Stand des zentralen Repositories (ohne merge).
Dies kann nützlich sein um erst ein Review vor dem git merge zu machen.
pull Macht das selbe wie "git fetch" aber zusätzlich noch "git merge".
push Lokale Commits zum zentralen Repository schicken.
Fortgeschrittene Funktionen
checkout Kann ältere Commits wiederherstellen (oder zu einem anderen Branch wechseln).
revert TODO
reset TODO
clean TODO
commit --amend Änderungen dem letzten Commit hinzufügen (nur erlaubt solange noch kein push erfolgt ist!).
rebase TODO
reflog TODO

Tipps & Tricks

Lokale Änderungen verwerfen

Folgende Befehle verwerfen alle Änderungen und stellen quasi den Server-Zustand her:

git reset --hard
git clean -df

Squash - (Lokale) Commits zusammenführen

Manchmal ist es praktisch Zwischenstände in kurzen Abständen zu committen, besonders um etwas auszuprobieren. Blöd nur wenn jedes "Mini"-Commit zum Server geschickt wird. In so einem Fall wünscht man sich eine Merge-Funktion der lokalen Commits um einen großen zu erhalten.

Achtung: Dieser Befehl darf nur für lokale Commits angewendet werden welche noch nicht zum Server gepusht wurden!

Folgt noch...
]]>
Content-Unterstützung via XAML für UserControls http://blog.rollnut.com/content-unterstuetzung-via-xaml-fuer-usercontrols/ Sat, 23 Jan 2016 14:20:48 +0000 http://blog.rollnut.com/?p=162 UserControls sind modulare bzw. fertige (Programm)-Komponenten die nach außen gekapselt sind. Manchmal währe es praktisch beim Verwenden eines UserControls, neben den üblichen Properties, den Inhalt direkt im XAML-Code zu definieren und einzubetten.

Ich habe zur Demonstration ein UserControl erstellt, welches ein Grid mit blauen Hintergrund enthält. In diesem Grid soll der Inhalt gezeigt werden, welcher vom Verwender definiert wird (<ContentPresenter />).

<UserControl 
    x:Class="ContentWPF.MyUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
    <Grid Background="Blue">
        <ContentPresenter />
    </Grid>
</UserControl>

Die Verwendung von dem UserControl wird wie üblich in XAML deklariert mit dem Unterschied das man diesmal direkt im XAML den Content angibt.

<local:MyUserControl>
    Hello World
</local:MyUserControl>

 Das Problem

Nun ist es so das am Ende nichts von der Definition des Controls (‚MyUserControl.xaml‘) gezeigt wird. Es wird nur noch „Hello World“ ohne Grid und ohne Blau dargestellt.

Der Grund ist, dass das Content-Property von UserControl mit dem Content des Verwenders überschrieben wird und dabei die eigentliche Definition verloren geht.

Mit diesem Wissen ist der nächste Schritt nun zu verhindern, dass das Content-Property überschrieben wird. Das machen wir einfach indem wir im CodeBehind von ‚MyUserControl‘ ein separates Content-Property anlegen.

public static readonly DependencyProperty MyContentProperty 
    = DependencyProperty.Register("MyContent", typeof(object), typeof(MyUserControl), null);
public object MyContent
{
    get { return (object)GetValue(MyContentProperty); }
    set { SetValue(MyContentProperty, value); }
}
<UserControl 
    x:Class="ContentWPF.MyUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
    <Grid Background="Blue">
        <ContentPresenter Content="{Binding Path=MyContent,
          RelativeSource={RelativeSource  AncestorType=UserControl}}" />
    </Grid>
</UserControl>

Mit dem neuen Property kann 'MyUserControl' folgenderweiße verwendet werden:

<local:MyUserControl MyContent="Hello World" />

Oder

<local:MyUserControl>
    <local:MyUserControl.MyContent>
        Hello World
    </local:MyUserControl.MyContent>
</local:MyUserControl>

(zweite Variante ist besser wenn kein Text sondern ein kompletter Control-Tree eingefügt wird).

Jetzt funktioniert es zwar, aber nur indem man beim Verwenden des Control mehr Code schreibt als nötig. Wer (so wie ich) sich daran stört unnötigen Code zu produzieren für den habe ich folgende ...

 Lösung 😀

Die Lösung ist so Simpel, allerdings wenn man es nicht weiß kann man nicht darauf kommen.

Man muss lediglich im CodeBehind von ‚MyUserControl‘ das Klassen-Attribute 'ContentPropertyAttribute' deklarieren und als Parameter das gewünschte Property als string angeben.

namespace ContentWPF
{
    [ContentProperty("MyContent")] 
    public partial class MyUserControl : UserControl
    {
        public MyUserControl()
        {
            InitializeComponent();
        }
        
        public static readonly DependencyProperty MyContentProperty
            = DependencyProperty.Register("MyContent", typeof(object), typeof(MyUserControl), null);
        public object MyContent
        {
            get { return (object)GetValue(MyContentProperty); }
            set { SetValue(MyContentProperty, value); }
        }
    }
}

Damit kann man nun im XAML-Code das Einbetten von Sub-Elementen (wie anfangs demonstriert) verwenden:

<local:MyUserControl>
    Hello World
</local:MyUserControl>

Böser Beigeschmack

Leider gibt es selbst jetzt noch einen Nachteil, den man wohl nicht umgehen kann, wenn man ein UserControl verwendet. Wenn ihr ‚MyUserControl‘ nun verwendet und einem Element darin einem Namen gebt werdet ihr vom Compiler informiert das dies nicht erlaubt ist.

<local:MyUserControl>
    <TextBlock x:Name="CompilerErrorIfItemIsNamed">Hello World</TextBlock>
</local:MyUserControl>

Folgender Fehler wird ausgegeben:

Cannot set Name attribute value 'CompilerErrorIfItemIsNamed' on element 'TextBlock'. 'TextBlock' is under the scope of element 'MyUserControl', which already had a name registered when it was defined in another scope.

Den Namen selbst kann man zwar nachträglich über CodeBehind (oder sonstwo) angeben aber zur Designzeit kann man dieses Element im CodeBehind nicht direkt ansprechen.

Wenn man damit nicht Leben kann führt kein weg an einem CustomControl vorbei. Bei diesem können eingebettete Elemente mit einem Namen versehen und vom CodeBehind direkt angesprochen werden.

Achtung: In Silverlight ist dieses Verhalten identisch, allerdings versucht euch der Compiler zu täuschen. Die Fehlermeldung wird dort nicht gezeigt und das Projekt lässt sich compilieren. Allerdings wenn Ihr zur Laufzeit im CodeBehind direkt auf das Element zugreift wird eine NullReferenceException fliegen da das Property immer leer ist.

]]>
CacheContainer http://blog.rollnut.com/cachecontainer/ Sun, 18 Oct 2015 16:09:22 +0000 http://blog.rollnut.com/?p=154 Stellt das Grundgerüst für einfaches Caching als abstrakte Basisklasse bereit.
Mit der generischen CacheContainer-Klasse kann kurzerhand mit einem Delegate ein fertiger Cache verwendet werden.

Das Problem:

Sellt euch vor Ihr benötigt zur Laufzeit die Datei ‚ExampleImage.png‘ welche sich auf einem Netzlaufwerk befindet. Je nach Qualität 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.

Beispiel:
In meinem Beispiel simuliere ich die 1 Sekunde Wartezeit indem der Thread einfach 1 Sekunde wartet. Außerdem wird für Analysezwecke noch die Dauer aufgezeichnet.

static void Main(string[] args)
{
    const string path = @"D:\\ExampleImage.png";
    var startDate = DateTime.Now;

    for (int i = 0; i < 10; i++)
    {
        byte[] imageAsBytes = null;

        using (var memStream = new MemoryStream())
        using (var stream = File.OpenRead(path))
        {
            stream.CopyTo(memStream);
            // Code above will take 1 second.
            Thread.Sleep(1000);

            imageAsBytes = memStream.ToArray();
        }

        // Do something amazing with the image!
        Console.WriteLine(string.Format("#{0}: {1}", i, imageAsBytes.Count()));
    }

    Console.WriteLine(string.Format("Finished after {0} sec.", (DateTime.Now - startDate).Seconds));
    Console.ReadLine();
}

Hier bietet sich Caching an. Das bedeutet das die Datei einmalig geladen und direkt zwischengespeichert wird um bei den nächsten Zugriffen einen erheblichen Geschwindigkeitsvorteil zu haben.

Ist-Situation

Wenn einfaches Caching in Dot.Net gebraucht wird wird dies häufig mit einem Dictionary realisiert. Dafür wird einfach vor dem öffnen des Streams geprüft 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ügt (wodurch der Key bei der nächsten Prüfung gefunden wird und ein Zugriff auf das Netzlaufwerk nicht mehr nötig ist).

private static readonly Dictionary<string, byte[]> _cache = new Dictionary<string, byte[]>();

static void Main(string[] args)
{
    const string path = @"D:\\Floor 1.jpeg";
    var startDate = DateTime.Now;

    for (int i = 0; i < 10; i++)
    {
        byte[] imageAsBytes = null;
        // !!! Check first if wanted image is already in cache !!!
        if (!_cache.TryGetValue(path, out imageAsBytes))
        {
            using (var memStream = new MemoryStream())
            using (var stream = File.OpenRead(path))
            {
                stream.CopyTo(memStream);
                // Code above will take 1 second.
                Thread.Sleep(1000);

                imageAsBytes = memStream.ToArray();
            }

            // !!! Very important to store the new value in cache !!!
            _cache.Add(path, imageAsBytes);
        }

        // Do something amazing with the image!
        Console.WriteLine(string.Format("#{0}: {1}", i, imageAsBytes.Count()));
    }

    Console.WriteLine(string.Format("Finished after {0} sec.", (DateTime.Now - startDate).Seconds));
    Console.ReadLine();
}

Mit dieser einfachen Änderung wurde ganze 9 Sekunden eingespart.

Wenn der Cache ordentlich implementiert wurde dann erfolgt der Zugriff außschließlich über Zugriffsmethoden und nicht inline (da caching häufig im größeren Rahmen Verwendung findet als nur innerhalb einer Methode). Außerdem wird so die Integrität des Caches gewährleistet.

(in unterem Beispiel wurde die Caching- und Lade-Logik in die Methode 'GetImageFromCache' ausgelagert.)

private static readonly Dictionary<string, byte[]> _cache = new Dictionary<string, byte[]>();

public static byte[] GetImageFromCache(string imagePath)
{
    if (String.IsNullOrEmpty(imagePath)) throw new ArgumentException("imagePath");

    byte[] imageAsBytes;
    // !!! Check first if wanted image is already in cache !!!
    if (!_cache.TryGetValue(imagePath, out imageAsBytes))
    {
        using (var memStream = new MemoryStream())
        using (var stream = File.OpenRead(imagePath))
        {
            stream.CopyTo(memStream);
            // Code above will take 1 second.
            Thread.Sleep(1000);

            imageAsBytes = memStream.ToArray();

            // !!! Very important to store the new value in cache !!!
            _cache.Add(imagePath, imageAsBytes);
        }
    }
    return imageAsBytes;
}

In der Main-Methode wird nun einfach die eben erstellte Methode aufgerufen (was dort nebenbei die Lesbarkeit des Codes erhöht).

static void Main(string[] args)
{
    const string path = @"D:\\Floor 1.jpeg";
    var startDate = DateTime.Now;

    for (int i = 0; i < 10; i++)
    {
        byte[] imageAsBytes = GetImageFromCache(path);

        // Do something amazing with the image!
        Console.WriteLine(string.Format("#{0}: {1}", i, imageAsBytes.Count()));
    }

    Console.WriteLine(string.Format("Finished after {0} sec.", (DateTime.Now - startDate).Seconds));
    Console.ReadLine();
}

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ültig oder veraltet ist. Außerdem besteht bei obigen Code immer die Gefahr das ein unwissender Kollege das originalle Dictionary direkt manipuliert und mir somit meine Integrität versaut.

Soll-Situation

Als Lösung habe ich das Caching-Verhalten, zusammen mit dem Dictionary, als Klasse gekapselt.
Bevor ich die Details dieser Klasse erkläre möchte ich vorerst das Ergebnis beim Verwenden dieser demonstrieren.

public static CacheContainer<string, byte[]> ImageCache { get; private set; }

static void Main(string[] args)
{
    const string path = @"D:\\Floor 1.jpeg";
    var startDate = DateTime.Now;

    // Initialize the Cache.
    ImageCache = new CacheContainer<string, byte[]>((key) =>
    {
        if (String.IsNullOrEmpty(key)) return null;

        byte[] imageAsBytes = null;

        using (var memStream = new MemoryStream())
        using (var stream = File.OpenRead(key))
        {
            stream.CopyTo(memStream);
            // Code above will take 1 second.
            Thread.Sleep(1000);

            imageAsBytes = memStream.ToArray();
        }
        return new CacheCallbackResult<byte[]>(true, imageAsBytes);
    });

    for (int i = 0; i < 10; i++)
    {
        // Get image from cache or load it from disc.
        var imageAsBytes = ImageCache.GetValue(path);

        // Do something amazing with the image!
        Console.WriteLine(string.Format("#{0}: {1}", i, imageAsBytes.Count()));
    }

    Console.WriteLine(string.Format("Finished after {0} sec.", (DateTime.Now - startDate).Seconds));
    Console.ReadLine();
}

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äter mehr). Kurz erklärt passiert in dem oben erstellten Delegate das gleiche wie in der Methode 'GetImageFromCache', aber mit dem Unterschied das die Prüfung des Methodenvertrags keinen Fehler wirft sondern stattdessen null zurück gibt.

Anschließend wird die Schleife 10mal aufgerufen welche die Methode ‚GetValue‘ von dem Cache-Objekt aufruft.

Von der Code-Menge gesehen hat sich wenig geändert, aber unter der Haube hat sich sehr viel getan.
Die Zugriffe gewährleisten nun immer das die Integrität erhalten bleibt, ThreadSafe und zusätzliche Methoden mit denen der Cache wieder geleert werden kann.

Die Basis-Klasse

Die Klasse wurde auf zwei Ebenen aufgeteilt. Die Parent-Klasse ist abstrakt und arbeitet ohne Delegate. Sie gewährleistet die Stabilität des Caches für alle erbenden Klassen.

public abstract class CacheContainerBase<T1, T2>
{
    private readonly object _sync = new object();
    private readonly Dictionary<T1, T2> _cacheDict = new Dictionary<T1, T2>();

    // .:: Methods - Essential

    public T2 GetValue(T1 key)
    {
        T2 value;
        if (!TryGetValue(key, out value))
        {
            throw new KeyNotFoundException(string.Format("The given key ({0}) is not supported to get a value.", (object)key ?? "NULL"));
        }
        return value;
    }

    public bool TryGetValue(T1 key, out T2 value)
    {
        bool hasGotValue = false;
        lock (_sync)
        {
            // Check if key exists in cache.
            hasGotValue = _cacheDict.TryGetValue(key, out value);
            if (!hasGotValue)
            {
                // Then get the value from abstract method which an inherited class ensures to return something.
                hasGotValue = TryGetNonCachedValue(key, out value);

                // Cache the result.
                if (hasGotValue)
                {
                    _cacheDict.Add(key, value);
                }
            }
        }
        return hasGotValue;
    }

    protected abstract bool TryGetNonCachedValue(T1 key, out T2 value);
}

Zwei generische Klassenparameter werden vorausgesetzt (Key und Value).
Das private Feld ‚_sync‘ soll die Klasse ThreadSafe machen und ‚_cacheDict‘ ist das Kernelement der ganzen Klasse und sollte aus dem Beispiel der Ist-Situation noch bekannt sein.

Die Methode ‚GetValue‘ versucht einen Wert anhand des übergebenen Keys zu finden indem sie die Methode ‚TryGetValue‘ aufruft. Sollte der Key nicht erlaubt sein fliegt eine ‚KeyNotFoundException‘-Exception.

‚TryGetValue‘ gibt ebenfalls einen Wert anhand des Keys zurück (über ‚out‘ Parameter) wirft aber keine Exception sondern gibt stattdessen false zurück.

Um einen Wert zu finden wird zuerst im Cache nachgesehen ob er bereits existiert. Wenn nicht wird die abstrakte Method ‚TryGetNonCachedValue‘ aufgerufen.

Um einer erbenden Klasse mehr Kontrolle über den Cache zu geben ohne das dieser gefährdet wird stellen wir weitere Methoden zu Manipulation bereit.

// .:: Methods - Clear Cache

protected void ClearCache()
{
    lock (_sync)
        _cacheDict.Clear();
}
protected void ClearCache(IEnumerable keys)
{
    if (keys == null) throw new ArgumentNullException("keys");

    lock (_sync)
    {
        foreach (var key in keys)
        {
            _cacheDict.Remove(key);
        }
    }
}

Die überladene ‚ClearCache‘-Methode macht das was Sie sagt. Entweder werden alle Einträge aus dem Cache entfernt oder nur für bestimmte Keys.

// .:: Methods - Cache Contains Key/Value

protected bool CacheContainsKey(T1 key)
{
    lock (_sync)
        return _cacheDict.ContainsKey(key);
}
protected bool CacheContainsValue(T2 value)
{
    lock (_sync)
        return _cacheDict.ContainsValue(value);
}

‚CacheContainsKey‘ oder ‚CacheContainsValue‘ geben je true zurück bei passender Übereinstimmung.

Zu erwähnen ist das alle vier Methoden das selbe Objekt für lock verwenden welches auch die Methode ‚TryGetValue‘ verwendet. Das führt dazu das jeder asynchrone Zugriff über alle Methoden hinweg synchronisiert wird und damit die Integrität des Caches auch bei asynchroner Verwendung sichergestellt ist.

Außerdem sind die vier Methoden als protected markiert um einer möglichen Child-Klasse Kontrolle über das Objekt zu geben ohne dabei nach außen Funktionalität zu öffnen (ein praktisches Anwendungsbeispiel wird später erläutert).

Cache-Klasse mithilfe von Delegate

public class CacheContainer<T1, T2> : CacheContainerBase<T1, T2>
{
    private readonly Func<T1, CacheCallbackResult> _tryGetNonCachedValueFunc;

    public CacheContainer(Func<T1, CacheCallbackResult> tryGetNonCachedValueFunc)
    {
        if (tryGetNonCachedValueFunc == null)
            throw new ArgumentNullException("tryGetNonCachedValueFunc");
        _tryGetNonCachedValueFunc = tryGetNonCachedValueFunc;
    }

    // .:: Methods - Essential

    protected override bool TryGetNonCachedValue(T1 key, out T2 value)
    {
        var result = _tryGetNonCachedValueFunc.Invoke(key);
        value = result == null
            ? default(T2)
            : result.Value;
        return result == null
            ? false
            : result.KeyIsValid;
    }
}

Die Child-Klasse überschreibt die abstrakte Methode vom Parent (TryGetNonCachedValue). Dort wird lediglich ein Delegate aufgerufen welches im Konstruktor der Klasse zwingend erforderlich ist.

Das Delegate muss als Parameter den Key (T1) entgegennehmen und den Value gekapselt in der Klasse CacheCallbackResult zurückgeben (dazu später mehr). Is das Result NULL oder aber dessen Property ‚IsKeyValid‘ false ist zu erwarten das der Key im Parameter nicht gültig ist und es kann false zurückgegeben werden.

// .:: Methods - Protected to Public

public new void ClearCache()
{
    base.ClearCache();
}
public new void ClearCache(IEnumerable keys)
{
    base.ClearCache(keys);
}

public new bool CacheContainsKey(T1 key)
{
    return base.CacheContainsKey(key);
}
public new bool CacheContainsValue(T2 value)
{
    return base.CacheContainsValue(value);
}

Da die Child-Klasse nach außen recht geschloßen ist empfehle ich die vier Methoden vom Parent welche protected sind nach außen freizugeben. Dies ist enorm hilfreich für Debugging-Zwecke.

Übrigens wird in der Child-Klasse komplett auf das ‚lock‘-Schlüßelwort verzichten da der Parent sich bereits darum kümmert.

CacheCallbackResult

Welcher Delegatetyp eignet sich am besten wenn man eine Methoden-Signatur benötigt welche einen Parameter und zwei Rückgabewerte verlangt? Bei einer normalen Methode würde man wohl einfach einen out Parameter verwenden was folgender Delegate-Signatur entspricht.

delegate bool TryGetNonCachedValueDelegate(T1 key, out T2 value);

Leider unterstützen Inline-Delegates keine 'out'-Parameter. Es währe sehr schade wenn der CacheContainer keine Inline-Schreibweise unterstützden würde. Daher bin ich zum Entschluss gekommen das man beide Results einfach in folgender Wrapper-Klasse zurückgibt und auf das 'out' verzichtet.

public class CacheCallbackResult
{
    public CacheCallbackResult(bool keyIsValid, T value)
    {
        this.KeyIsValid = keyIsValid;
        this.Value = value;
    }
    public bool KeyIsValid { get; private set; }
    public T Value { get; private set; }
}

Der erste Parameter 'keyIsValid' enthält die Information ob der Key eine gültige Struktur aufweist (ungültige Keys geben natürlich keinen Wert zurück und werden nicht im Cache gehalten).
Als zweiten, generischen, Parameter 'value' wird der Value erwartet welcher zu dem Key gefunden wurde.

Der komplette Code

Den kompletten Code könnt Ihr unten (mit Kommentaren) finden. Ich habe dort noch zwei Ergänzung in der Klasse ‚CacheContainerBase‘ gemacht welche ebenfalls hilfreich sind.

1. Ein zweiter Cache welcher sich alle Invaliden Keys merkt.
2. Ein Property ‚CanCacheInvalidKeys‘ welches bei false obiges Verhalten unterdrückt.

CacheContainerBase

/// <summary>
/// Provide basic caching by key in a thread safe way.
/// </summary>
/// <typeparam name="T1">The key type.</typeparam>
/// <typeparam name="T2">The value type.</typeparam>
public abstract class CacheContainerBase<T1, T2>
{
    // .:: Fields

    /// <summary>
    /// Sync all method calls to avoid async problems.
    /// </summary>
    private readonly object _sync = new object();

    /// <summary>
    /// The cache for valid keys.
    /// </summary>
    private readonly Dictionary<T1, T2> _cacheDict = new Dictionary<T1, T2>();

    // The hashset is the better collection for the job of caching invalid keys, but not available in pcl.
    //private readonly HashSet<T1> _cache_InvalidKeys = new HashSet<T1>();
    /// <summary>
    /// The cache for invalid keys.
    /// </summary>
    private readonly Dictionary<T1, byte> _cache_InvalidKeys = new Dictionary<T1, byte>();


    // .:: Constructor

    public CacheContainer()
    {
        this.CanCacheInvalidKeys = true;
    }


    // .:: Properties

    /// <summary>
    /// Set to false in order to avoid caching for keys which are not valid to request.
    /// </summary>
    protected bool CanCacheInvalidKeys { get; set; }


    // .:: Methods - Essential

    /// <summary>
    /// Get the value which belongs to given key.
    /// </summary>
    /// <param name="key">The expected value belongs to this key.</param>
    /// <exception cref="KeyNotFoundException">The key is not valid for this cache.</exception>
    public T2 GetValue(T1 key)
    {
        T2 value;
        if (!TryGetValue(key, out value))
        {
            throw new KeyNotFoundException(string.Format("The given key ({0}) is not supported to get a value.", (object)key ?? "NULL"));
        }
        return value;
    }
    /// <summary>
    /// Get the value which belongs to given key.
    /// </summary>
    /// <param name="key">The expected value belongs to this key.</param>
    /// <param name="value">The expected value.</param>
    /// <returns>Returns true if key is valid. False if key is invalid to this cache.</returns>
    public bool TryGetValue(T1 key, out T2 value)
    {
        bool hasGotValue = false;
        lock (_sync)
        {
            // 1. Check if key exists in cache.
            hasGotValue = _cacheDict.TryGetValue(key, out value);
            if (!hasGotValue)
            {
                // 2. Check if key exists in invalid key cache.
                bool isKeyInvalid = this.CanCacheInvalidKeys
                    ? _cache_InvalidKeys.ContainsKey(key)
                    : false;
                if (isKeyInvalid)
                {
                    hasGotValue = false;
                    value = default(T2);
                }
                else
                {
                    // 3. Then get the value from abstract method which an inherited class ensures to return something.
                    hasGotValue = TryGetNonCachedValue(key, out value);

                    // 3.1 Cache the result.
                    if (hasGotValue)
                    {
                        _cacheDict.Add(key, value);
                    }
                    // 3.2 Cache the result in invalid keys.
                    else if (this.CanCacheInvalidKeys)
                    {
                        _cache_InvalidKeys.Add(key, byte.MinValue);
                    }
                }
            }
        }
        return hasGotValue;
    }


    /// <summary>
    /// This method is only called if given key was not found in cache.
    /// The abstract method should return a value (out parameter) which belongs to given key 
    /// and a bool which is true when given key was valid.
    /// The returned value will be cached then.
    /// </summary>
    /// <param name="key">The expected value belongs to this key.</param>
    protected abstract bool TryGetNonCachedValue(T1 key, out T2 value);


    // .:: Methods - Clear Cache

    /// <summary>
    /// Clear all cached entries.
    /// </summary>
    /// <remarks> Clear in normal cache and invalid key cache.</remarks>
    protected void ClearCache()
    {
        lock (_sync)
        {
            _cacheDict.Clear();
            _cache_InvalidKeys.Clear();
        }
    }
    /// <summary>
    /// Clear only cached entries which depends to given keys.
    /// </summary>
    /// <param name="keys">Remove only this keys from cache.</param>
    /// <remarks> Clear in normal cache and invalid key cache.</remarks>
    protected void ClearCache(IEnumerable<T1> keys)
    {
        if (keys == null) throw new ArgumentNullException("keys");

        lock (_sync)
        {
            foreach (var key in keys)
            {
                // 1. Remove from cache.
                if (!_cacheDict.Remove(key))
                {
                    // 2. Remove from invalid key cache.
                    _cache_InvalidKeys.Remove(key);
                }
            }
        }
    }


    // .:: Methods - Cache Contains Key/Value

    /// <summary>
    /// Check if given key is present in cache.
    /// </summary>
    /// <param name="key">The key which is present in cache.</param>
    /// <remarks> Check in normal cache and invalid key cache.</remarks>
    protected bool CacheContainsKey(T1 key)
    {
        lock (_sync)
        {
            return _cacheDict.ContainsKey(key)
                ? true
                : _cache_InvalidKeys.ContainsKey(key);
        }
    }
    /// <summary>
    /// Check if given value is present in cache.
    /// </summary>
    /// <param name="value">The value which is present in cache.</param>
    protected bool CacheContainsValue(T2 value)
    {
        lock (_sync)
            return _cacheDict.ContainsValue(value);
    }
}

CacheContainer

/// <summary>
/// Provide basic caching by key.
/// Get value is supported by a delegate from outside (no inheritance is required).
/// </summary>
/// <typeparam name="T1">The key type.</typeparam>
/// <typeparam name="T2">The value type.</typeparam>
/// <remarks>
/// Hinweis auf delegate und anwendungsbeispiel
/// und warum alle protected methoden public wurden
/// </remarks>
public class CacheContainer<T1, T2> : CacheContainerBase<T1, T2>
{
    /// <summary>
    /// Delegate which return a value for given key.
    /// </summary>
    private readonly Func<T1, CacheCallbackResult<T2>> _tryGetNonCachedValueFunc;

    /// <summary>
    /// Cache by key. Value is provided by given delegate.
    /// </summary>
    /// <param name="tryGetNonCachedValueFunc">The deleagte which returns a value for given key as 
    /// the 'CacheCallbackResult'-object (or the flase 'KeyIsValid'-flag).</param>
    public CacheContainer(Func<T1, CacheCallbackResult<T2>> tryGetNonCachedValueFunc)
    {
        if (tryGetNonCachedValueFunc == null) throw new ArgumentNullException("tryGetNonCachedValueFunc");
        _tryGetNonCachedValueFunc = tryGetNonCachedValueFunc;
    }

    // .:: Methods - Essential

    /// <summary>
    /// Overridden method which returns the value from key by invoke given delegate.
    /// </summary>
    /// <param name="key">The key to get the value.</param>
    /// <param name="value">The value which is excpected by given key.</param>
    /// <returns>False if given key is not supported.</returns>
    protected override bool TryGetNonCachedValue(T1 key, out T2 value)
    {
        var result = _tryGetNonCachedValueFunc.Invoke(key);
        value = result == null
            ? default(T2)
            : result.Value;
        return result == null
            ? false
            : result.KeyIsValid;
    }

    // .:: Methods - Protected to Public

    /// <summary>
    /// Clear all cached entries.
    /// </summary>
    /// <remarks> Clear in normal cache and invalid key cache.</remarks>
    public new void ClearCache()
    {
        base.ClearCache();
    }
    /// <summary>
    /// Clear only cached entries which depends to given keys.
    /// </summary>
    /// <param name="keys">Remove only this keys from cache.</param>
    /// <remarks> Clear in normal cache and invalid key cache.</remarks>
    public new void ClearCache(IEnumerable<T1> keys)
    {
        base.ClearCache(keys);
    }

    /// <summary>
    /// Check if given key is present in cache.
    /// </summary>
    /// <param name="key">The key which is present in cache.</param>
    public new bool CacheContainsKey(T1 key)
    {
        return base.CacheContainsKey(key);
    }
    /// <summary>
    /// Check if given value is present in cache.
    /// </summary>
    /// <param name="value">The value which is present in cache.</param>
    public new bool CacheContainsValue(T2 value)
    {
        return base.CacheContainsValue(value);
    }
}

/// <summary>
/// A helper class for CacheContainer to support a 'TryGetValue'-Behavior.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CacheCallbackResult<T>
{
    public CacheCallbackResult(bool keyIsValid, T value)
    {
        this.KeyIsValid = keyIsValid;
        this.Value = value;
    }
    public CacheCallbackResult(Exception ex)
    {
        this.Exception = ex;
    }
    public bool KeyIsValid { get; private set; }
    public T Value { get; private set; }
    public Exception Exception { get; set; }
}
]]>