I have a problem I can't seem to wrap my head around. I am creating a class to hold a dictionary of items that have generic types. The problem I'm faced with is needed to force this dictionary to be InvariantCultureIgnoreCase if the index type is a string.
For example:
public class Cache<TIndex, TItem>
{
protected IDictionary<TIndex, TItem> _cache { get; set; }
public Cache()
{
this._cache = new Dictionary<TIndex, TItem>();
}
public bool Exists(TIndex index)
{
if (!_cache.ContainsKey(index))
{
//....do other stuff here
this._cache.Add(databaseResult.Key, databaseResult.Value);
return false;
}
return true;
}
}
So the first problem was dealing with strings that had various capitalization; I solved this by forcing the data to be upper-case. Now, however, I've found that there are some characters which are culture specific, so without the invariant culture switch, ContainsKey will return false.
I've tried creating a new IEqualityComparer, but that never gets fired. Any ideas?
Please try the following:
public Cache()
{
if (typeof(TIndex) == typeof(string))
{
this._cache = new Dictionary<TIndex, TItem>((IEqualityComparer<TIndex>)StringComparer.InvariantCultureIgnoreCase);
}
else
{
this._cache = new Dictionary<TIndex, TItem>();
}
}
Or (with ternary operator):
public Cache()
{
this._cache = typeof(TIndex) == typeof(string)
? new Dictionary<TIndex, TItem>((IEqualityComparer<TIndex>)StringComparer.InvariantCultureIgnoreCase)
: new Dictionary<TIndex, TItem>();
}
Or (really short, as suggested by #Rawling):
public Cache()
{
this._cache = new Dictionary<TIndex, TItem>(StringComparer.InvariantCultureIgnoreCase as IEqualityComparer<TIndex>);
}
Here's a fully functional version of what I THINK you're asking for (I had to add a Set function, otherwise it is based on your own code). It is, as the if/Console.WriteLine checks show, ignoring case. If this isn't what you're looking for, please clarify your question further.
class Program
{
static void Main(string[] args)
{
Cache<string, string> stringCache = new Cache<string, string>();
stringCache.Set("A String Index", "A String Item");
if (stringCache.Exists("A String Index"))
Console.WriteLine("Title Case exists");
if (stringCache.Exists("A STRING INDEX"))
Console.WriteLine("All Caps Exists");
if (stringCache.Exists("a string index"))
Console.WriteLine("All Lowercase Exists");
}
}
class Cache<TIndex, TItem>
{
private IDictionary<TIndex, TItem> _cache { get; set; }
public Cache()
{
if (typeof(TIndex) == typeof(string))
{
_cache = new Dictionary<TIndex, TItem>((IEqualityComparer<TIndex>)StringComparer.InvariantCultureIgnoreCase);
}
else
{
_cache = new Dictionary<TIndex, TItem>();
}
}
public void Set(TIndex index, TItem item)
{
_cache[index] = item;
}
public bool Exists(TIndex index)
{
if (!_cache.ContainsKey(index))
{
return false;
}
return true;
}
}
Related
C# has the usefull Null Conditional Operator. Well explained in this answer too.
I was wondering if it is possible to do a similar check like this when my object is a dynamic/expando object. Let me show you some code:
Given this class hierarchy
public class ClsLevel1
{
public ClsLevel2 ClsLevel2 { get; set; }
public ClsLevel1()
{
this.ClsLevel2 = new ClsLevel2(); // You can comment this line to test
}
}
public class ClsLevel2
{
public ClsLevel3 ClsLevel3 { get; set; }
public ClsLevel2()
{
this.ClsLevel3 = new ClsLevel3();
}
}
public class ClsLevel3
{
// No child
public ClsLevel3()
{
}
}
If i perform this kind of chained null check, it works
ClsLevel1 levelRoot = new ClsLevel1();
if (levelRoot?.ClsLevel2?.ClsLevel3 != null)
{
// will enter here if you DO NOT comment the content of the ClsLevel1 constructor
}
else
{
// will enter here if you COMMENT the content of the ClsLevel1
}
Now, i will try to reproduce this behaviour with dynamics (ExpandoObjects)
dynamic dinRoot = new ExpandoObject();
dynamic DinLevel1 = new ExpandoObject();
dynamic DinLevel2 = new ExpandoObject();
dynamic DinLevel3 = new ExpandoObject();
dinRoot.DinLevel1 = DinLevel1;
dinRoot.DinLevel1.DinLevel2 = DinLevel2;
//dinRoot.DinLevel1.DinLevel2.DinLevel3 = DinLevel3; // You can comment this line to test
if (dinRoot?.DinLevel1?.DinLevel2?.DinLevel3 != null)
{
// Obviously it will raise an exception because the DinLevel3 does not exists, it is commented right now.
}
Is there a way to simulate this behaviour with dynamics? I mean, check for a null in a long chain of members?
If you want to support this in a more natural way you can inherit from DynamicObject and provide a custom implementation:
class MyExpando : DynamicObject
{
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name.ToLower();
result = _dictionary.ContainsKey(name) ? _dictionary[name] : null;
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dictionary[binder.Name.ToLower()] = value;
return true;
}
}
Testing:
private static void Main(string[] args)
{
dynamic foo = new MyExpando();
if (foo.Boo?.Lol ?? true)
{
Console.WriteLine("It works!");
}
Console.ReadLine();
}
The output will be "It works!". Since Boo does not exist we get a null reference so that the Null Conditional Operator can work.
What we do here is to return a null reference to the output parameter of TryGetMember every time a property is not found and we always return true.
EDIT: fixed, as ExpandoObjects and extension methods do not work well together. Slightly less nice, but hopefully still usable.
Helper method(s):
public static class DynamicExtensions
{
public static Object TryGetProperty(ExpandoObject obj, String name)
{
return name.Split('.')
.Aggregate((Object)obj, (o, s) => o != null
? TryGetPropertyInternal(o, s)
: null);
}
private static Object TryGetPropertyInternal(Object obj, String name)
{
var dict = obj as IDictionary<String, Object>;
return (dict?.ContainsKey(name) ?? false) ? dict[name] : null;
}
}
Usage:
if (DynamicExtensions.TryGetProperty(dinRoot, "DinLevel1.DinLevel2.DinLevel3") != null)
I have a bit of a complex dictionary.
It's a dictionary which holds two enumerated types & a List<>
Dictionary<BiomeType, Dictionary<LocationType, List<string>>> myDictionary;
So when I want to use it, I do something like this:
//Add "myString" to the List<string>
myDictionary[BiomeType.Jungle][LocationType.City1].Add("myString"));
When I try to add "myString" to myList, it throws an obvious & foreseeable error: "KeyNotFoundException: The given key was not present in the dictionary."
Is there any way in C# to automatically have the Dictionary add the Key if it isn't already there? I have a lot of BiomeTypes & even more LocationTypes. It would be a PITA to have to create each List, then create each locationtype dictionary, and then to add it for every BiomeType. All that work just to initialize this complex dictionary. Is there no easy way to do this?
I'm using this for gamedev, to store objects in a Dictionary, so I can access them by doing something like
BiomeType playerCurrentBiomeType;
LocationType playerCurrentLocationType;
LoadLevel(myDictionary[playerCurrentBiomeType][playerCurrentLocationType]);
//ex. myDictionary[BiomeType.Jungle][LocationType.CapitalCity]
//ex. myDictionary[BiomeType.Desert][LocationType.CapitalCity]
//ex. myDictionary[BiomeType.Desert][LocationType.City3]
Perhaps, you can try this:
Dictionary<BiomeType, Dictionary<LocationType, List<string>>> myDictionary = new Dictionary<BiomeType, Dictionary<LocationType, List<string>>>();
BiomeType playerCurrentBiomeType;
LocationType playerCurrentLocationType;
if(!myDictionary.ContainsKey(playerCurrentBiomeType))
{
myDictionary.Add(playerCurrentBiomeType, new Dictionary<LocationType , List<string>>{{playerCurrentLocationType, new List<string>()}});
}
myDictionary[playerCurrentBiomeType][playerCurrentLocationType].Add("bla");
You could do this (although to be honest I'm not sure you should!)
The class below is a type that generally acts like a dictionary, does what you asked for, and has some other changes to hide from you the empty items it creates every time you ask the indexer for an item that doesn't exist.
public class SmellyDictionary<T1, T2>: IDictionary<T1, T2>, ICollection where T2 : ICollection, new()
{
private readonly IDictionary<T1, T2> _dict = new Dictionary<T1, T2>();
public T2 this[T1 key]
{
get
{
T2 value;
if (!_dict.TryGetValue(key, out value))
_dict[key] = value = new T2(); // This stinks!
return value;
}
set { _dict[key] = value; }
}
public bool Contains(KeyValuePair<T1, T2> item)
{
return _dict.Contains(item);
}
public bool ContainsKey(T1 key)
{
return _dict.ContainsKey(key) && _dict[key].Count > 0; // This hides the smell
}
public int Count { get { return _dict.Count(kvp => kvp.Value.Count > 0); } } // This hides the smell
public void Add(T1 key, T2 value)
{
T2 currentValue;
if (_dict.TryGetValue(key, out currentValue) && currentValue.Count > 0)
throw new ArgumentException("A non empty element with the same key already exists in the SmellyDictionary");
_dict[key] = value;
}
public void Add(KeyValuePair<T1, T2> item)
{
Add(item.Key, item.Value);
}
public bool Remove(T1 key)
{
return _dict.Remove(key);
}
public bool Remove(KeyValuePair<T1, T2> item)
{
return _dict.Remove(item);
}
public bool TryGetValue(T1 key, out T2 value)
{
return _dict.TryGetValue(key, out value);
}
public ICollection<T1> Keys { get { return _dict.Keys; } }
public ICollection<T2> Values { get { return _dict.Values; } }
public object SyncRoot { get { return ((ICollection)_dict).SyncRoot; } }
public bool IsSynchronized { get { return ((ICollection)_dict).IsSynchronized; } }
public IEnumerator<KeyValuePair<T1, T2>> GetEnumerator()
{
return _dict.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Clear()
{
_dict.Clear();
}
public void CopyTo(Array array, int index)
{
_dict.CopyTo((KeyValuePair<T1, T2>[])array, index);
}
public void CopyTo(KeyValuePair<T1, T2>[] array, int arrayIndex)
{
_dict.CopyTo(array, arrayIndex);
}
public bool IsReadOnly { get { return _dict.IsReadOnly; } }
}
Here's a slightly more sensible option. Just call this method to add a string to your dictionary.
private void AddCityToDictionary(Dictionary<BiomeType, Dictionary<LocationType, List<string>>> myDictionary, BiomeType biome, LocationType location, string city)
{
Dictionary<LocationType, List<string>> locationDictionary;
if (!myDictionary.TryGetValue(biome, out locationDictionary))
locationDictionary = myDictionary[biome] = new Dictionary<LocationType, List<string>>();
List<string> cityList;
if (!locationDictionary.TryGetValue(location, out cityList))
cityList = locationDictionary[location] = new List<string>();
cityList.Add(city);
}
Simply looping through every possible enum type & adding in a new value works to fully populate this multi-dimensional dictionary.
Dictionary<BiomeType, Dictionary<LocationType, List<string>>> myDictionary = new Dictionary<BiomeType, Dictionary<LocationType, List<string>>>(); //No thanks to troll users like Peter.
foreach (BiomeType biomeType in System.Enum.GetValues(typeof(BiomeType)))
{
Dictionary<LocationType, List<string>> newLocDict = new Dictionary<LocationType, List<string>>(); //No thanks to troll users like Peter.
foreach (LocationType locType in System.Enum.GetValues(typeof(LocationType)))
{
List<string> newList = new List<string>();
newLocDict.Add(locType, newList); //Add the final bit here & voila! Finished! No thanks to troll users like Peter.
}
myDictionary.Add(biomeType, newLocDict);
}
Robyn's solution works the same way if you don't want to fully populate the container with ALL enum values.
I have a scenario where I need the properties in my class to map to a dictionary. Here is a code sample:
public string Foo
{
get
{
if (!PropertyBag.ContainsKey("Foo"))
{
return null;
}
return PropertyBag["Foo"];
}
set
{
PropertyBag["Foo"] = value;
}
}
I have to apply this pattern to multiple properties. Is there a way to use attributes to do that?
I know that PostSharp would work for this purpose, but I was hoping there is a way to do it without using it.
This feels like a code smell to me. It would be better to use regular POCOs and convert them to a Dictionary only when needed.
public class BlogPost
{
public string Title { get; set; }
public string Body { get; set; }
public int AuthorId { get; set; }
public Dictionary<string, object> ToDictionary()
{
return this.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(this, null));
}
}
Inspiration: How to convert class into Dictionary?
And to be honest, a ToDictionary method on your POCO's seems like a code smell. It would be better to refactor your code so the conversion of POCOs to Dictionaries happens in its own layer, as a service maybe.
Edit: This Gist I found while searching google for "c# convert object to dictionary" could provide a more generalized solution, and probably more bullet proof than my cobbled together example:
Gist: https://gist.github.com/jarrettmeyer/798667
From the Gist:
public static class ObjectToDictionaryHelper
{
public static IDictionary<string, object> ToDictionary(this object source)
{
return source.ToDictionary<object>();
}
public static IDictionary<string, T> ToDictionary<T>(this object source)
{
if (source == null)
ThrowExceptionWhenSourceArgumentIsNull();
var dictionary = new Dictionary<string, T>();
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
AddPropertyToDictionary<T>(property, source, dictionary);
return dictionary;
}
private static void AddPropertyToDictionary<T>(PropertyDescriptor property, object source, Dictionary<string, T> dictionary)
{
object value = property.GetValue(source);
if (IsOfType<T>(value))
dictionary.add(property.Name, (T)value);
}
private static bool IsOfType<T>(object value)
{
return value is T;
}
private static void ThrowExceptionWhenSourceArgumentIsNull()
{
throw new ArgumentNullException("source", "Unable to convert object to a dictionary. The source object is null.");
}
}
Credit: jerrettmeyer at GitHub
This should add a ToDictionary method to every object.
Edit: From the following comment
To give a bit of context, I am using Entity Framework and I have a class hierarchy that I would like to keep in one table while avoiding null columns everywhere.
Entity framework supports multiple table inheritance. That might be a better solution in your case.
You can write a GetValueOrDefault extension method and reduce the code a little.
public static class DictionaryExtensions
{
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey,TValue> self, TKey key)
{
TValue value;
self.TryGetValue(key,out value);
return value;
}
}
public string Foo
{
get
{
return PropertyBag.GetValueOrDefault("Foo");
}
set
{
PropertyBag["Foo"] = value;
}
}
You can eliminate the magic strings using expressions.
If you're using at least .NET 4.5 then you have the CallerMemberNameAttribute which you could use like this:
class SomeClass
{
public string Foo
{
get
{
return GetPropertyValue();
}
set
{
SetPropertyValue( value );
}
}
private string GetPropertyValue( [CallerMemberName] string name = null )
{
string value;
PropertyBag.TryGetValue( name, out value );
return value;
}
private void SetPropertyValue( string value, [CallerMemberName] string name = null )
{
PropertyBag[name] = value;
}
}
This will result in the compiler filling out the name of the member for you. If you're not (or otherwise can't) use .NET 4.5, another alternative would be to take advantage of expression trees as suggested in another answer.
class Test
{
Dictionary<string,object> _values = new Dictionary<string, object>();
public string Foo
{
get
{
var value = GetValue();
return value == null ? string.Empty : (string)value;
}
set
{
SetValue(value);
}
}
private object GetValue()
{
var stack = new StackTrace();
var key = GetGenericName(stack.GetFrame(1).GetMethod().Name);
if (_values.ContainsKey(key)) return _values[key];
return null;
}
private void SetValue(object value)
{
var stack = new StackTrace();
var key = GetGenericName(stack.GetFrame(1).GetMethod().Name);
_values[key] = value;
}
private string GetGenericName(string key)
{
return key.Split('_')[1];
}
}
I'm devising a template language. In it, there are 3 kinds of tokens: tags, directives, and variables. Each of these tokens have a name, and there's getting to be quite a few of them. They're extensible too.
To allow name reuse I want to add namespaces.
Right now all the variables are just stored in a dict. The key is the variable name, and the value is the variable value. That way I can quickly retrieve the value of a variable. However, supposing I want to allow dot-notation, namespace.variable, how can I store these variables, such that the namespace is optional? If the namespace is included the dict should only scan that namespace, if not, I guess it scans all namespaces.
Is there a container that will do this?
You should structure your symbol data internally as a dictionary of dictionary of string. The top level dictionary is for namespaces, and each dictionary below each namespace name is the container for all symbols in that namespace.
Looking up an unqualified symbol is simply a matter of looking for the symbol in each namespace in a particular order. In C# or Delphi, the order is determined by the order in which the namespaces are declared at the top of the source file, in reverse order of declaration (most recent is the first to be searched).
You can create your own implementation of IDictionary<string, object> instead of using the framework's Dictionary<TKey, TValue>.
Externally, there would be no change to the way you are consuming it.
Internally, it would consist of a Dictionary<string, Dictionary<string, object>>.
So, if your dictionary is asked for the value matching key "namespace.variable", internally it would split that string, get the Dictionary<string, Dictionary<string, object>> with key "namespace" and then return the value in that Dictionary<string, object> for key "variable."
To make the namespace optional, you have one entry where the key is string.Empty. Whether adding or getting items, any time a key is provided that does not contain ., you'll use the entry with key string.Empty.
My solution:
Class
public class NamespaceDictionary<T> : IDictionary<string, T>
{
private SortedDictionary<string, Dictionary<string, T>> _dict;
private const char _separator = '.';
public NamespaceDictionary()
{
_dict = new SortedDictionary<string, Dictionary<string, T>>();
}
public NamespaceDictionary(IEnumerable<KeyValuePair<string, T>> collection)
: this()
{
foreach (var item in collection)
Add(item);
}
#region Implementation of IEnumerable
public IEnumerator<KeyValuePair<string, T>> GetEnumerator()
{
return _dict.SelectMany(x => x.Value).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
private static Tuple<string, string> Split(string name)
{
int pos = name.LastIndexOf(_separator);
string ns = pos == -1 ? "" : name.Substring(0, pos);
string var = name.Substring(pos + 1);
return new Tuple<string, string>(ns, var);
}
#region Implementation of ICollection<KeyValuePair<string,TValue>>
public void Add(KeyValuePair<string, T> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
_dict.Clear();
}
public bool Contains(KeyValuePair<string, T> item)
{
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<string, T>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool Remove(KeyValuePair<string, T> item)
{
return Remove(item.Key);
}
public int Count
{
get { return _dict.Sum(p => p.Value.Count); }
}
public bool IsReadOnly
{
get { return false; }
}
#endregion
#region Implementation of IDictionary<string,TValue>
public bool ContainsKey(string name)
{
var tuple = Split(name);
return ContainsKey(tuple.Item1, tuple.Item2);
}
public bool ContainsKey(string ns, string key)
{
if (ns == "")
return _dict.Any(pair => pair.Value.ContainsKey(key));
return _dict.ContainsKey(ns) && _dict[ns].ContainsKey(key);
}
public void Add(string name, T value)
{
var tuple = Split(name);
Add(tuple.Item1, tuple.Item2, value);
}
public void Add(string ns, string key, T value)
{
if (!_dict.ContainsKey(ns))
_dict[ns] = new Dictionary<string, T>();
_dict[ns].Add(key, value);
}
public bool Remove(string ns, string key)
{
if (_dict.ContainsKey(ns) && _dict[ns].ContainsKey(key))
{
if (_dict[ns].Count == 1) _dict.Remove(ns);
else _dict[ns].Remove(key);
return true;
}
return false;
}
public bool Remove(string key)
{
var tuple = Split(key);
return Remove(tuple.Item1, tuple.Item2);
}
public bool TryGetValue(string name, out T value)
{
var tuple = Split(name);
return TryGetValue(tuple.Item1, tuple.Item2, out value);
}
public bool TryGetValue(string ns, string key, out T value)
{
if (ns == "")
{
foreach (var pair in _dict)
{
if (pair.Value.ContainsKey(key))
{
value = pair.Value[key];
return true;
}
}
}
else if (_dict.ContainsKey(ns) && _dict[ns].ContainsKey(key))
{
value = _dict[ns][key];
return true;
}
value = default(T);
return false;
}
public T this[string ns, string key]
{
get
{
if (ns == "")
{
foreach (var pair in _dict)
if (pair.Value.ContainsKey(key))
return pair.Value[key];
}
else if (_dict.ContainsKey(ns) && _dict[ns].ContainsKey(key))
return _dict[ns][key];
throw new KeyNotFoundException();
}
set
{
if (!_dict.ContainsKey(ns))
_dict[ns] = new Dictionary<string, T>();
_dict[ns][key] = value;
}
}
public T this[string name]
{
get
{
var tuple = Split(name);
return this[tuple.Item1, tuple.Item2];
}
set
{
var tuple = Split(name);
this[tuple.Item1, tuple.Item2] = value;
}
}
public ICollection<string> Keys
{
get { return _dict.SelectMany(p => p.Value.Keys).ToArray(); }
}
public ICollection<T> Values
{
get { return _dict.SelectMany(p => p.Value.Values).ToArray(); }
}
#endregion
}
Test
var dict = new NamespaceDictionary<int>();
dict.Add("ns1.var1", 1);
dict.Add("ns2.var1", 2);
dict.Add("var2", 3);
dict.Add("ns2.var2", 4);
dict.Add("ns3", "var1", 5);
dict["ns4.var1"] = 6;
Console.WriteLine(dict["var1"]);
Console.WriteLine(dict["ns2.var1"]);
Console.WriteLine(dict["var2"]);
Console.WriteLine(dict["ns2.var2"]);
Console.WriteLine(dict["ns2", "var2"]);
Console.WriteLine(dict["ns3.var1"]);
Console.WriteLine(dict["ns4", "var1"]);
Output
1
2
3
4
4
5
6
Help
I used a SortedDictionary thinking it would retain the order that the namespaces were added, but it's actually sorting the namespaces alphabetically. Is there an dict class that will retain the order the items were added, but not sort them?
I need to create an web module, with this module i need to fetch the title of some web site, after i will find that title i need to store that in thread safe caching mechanism and i need to save there the 10 lat fetched titles.
Any help please ?
Writing some locking code would be fairly easy except for...
How do you want to retrieve it? Do you want to be able to enumerate (foreach) over the list in a thread-safe fashion? There are a number of different ways to do that part, each with trade-offs.
You could go with the default behavior
This probably won't work well -- you'll get an exception if someone changes the list while you are enumerating it.
You could lock the collection during the whole course of the enumeration. This means that any thread attempting to add to your cache will be blocked until the foreach loop exits.
You could copy the collection internally each time you enumerate it and enumerate the copy.
This means that if someone adds to your list while you are enumerating it, you won't "see" the change.
For a list of ten, I'd go with the last option. (copy internally).
You code would look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Enumerator
{
class Program
{
static void Main(string[] args)
{
MyCache<string> cache = new MyCache<string>();
cache.Add("test");
foreach (string item in cache)
Console.WriteLine(item);
Console.ReadLine();
}
}
public class MyCache<T>: System.Collections.IEnumerable
{
private readonly LinkedList<T> InternalCache = new LinkedList<T>();
private readonly object _Lock = new Object();
public void Add(T item)
{
lock (_Lock)
{
if (InternalCache.Count == 10)
InternalCache.RemoveLast();
InternalCache.AddFirst(item);
}
}
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
// copy the internal cache to an array. We'll really be enumerating that array
// our enumeration won't block subsequent calls to Add, but this "foreach" instance won't see Adds either
lock (_Lock)
{
T[] enumeration = new T[InternalCache.Count];
InternalCache.CopyTo(enumeration, 0);
return enumeration.GetEnumerator();
}
}
#endregion
}
}
<B>EDIT 1:</B>
After sharing some comments with Rob Levine (below), I thought I'd throw a couple other alternatives out there.
This version allows you to iterate the collection lock-free. However, the Add() method is a little more expensive, as it must copy the list (moved the expense off of the Enumerate, and onto the add).
public class Cache2<T>: IEnumerable<T>
{
// changes occur to this list, and it is copied to ModifyableList
private LinkedList<T> ModifyableList = new LinkedList<T>();
// This list is the one that is iterated by GetEnumerator
private volatile LinkedList<T> EnumeratedList = new LinkedList<T>();
private readonly object LockObj = new object();
public void Add(T item)
{
// on an add, we swap out the list that is being enumerated
lock (LockObj)
{
if (this.ModifyableList.Count == 10)
this.ModifyableList.RemoveLast();
this.ModifyableList.AddFirst(item);
this.EnumeratedList = this.ModifyableList;
// the copy needs to happen within the lock, so that threaded calls to Add() remain consistent
this.ModifyableList = new LinkedList<T>(this.ModifyableList);
}
}
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
IEnumerable<T> enumerable = this.EnumeratedList;
return enumerable.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
System.Collections.IEnumerable enumerable = this.EnumeratedList;
return enumerable.GetEnumerator();
}
#endregion
}
<B>Edit 2:</B>
In the last example, we had a really inexpensive iteration, with the trade-off being a more expensive call to Add(). Next, I thought about using a ReaderWriterLockSlim (this is a .Net 3.5 object -- the old ReaderWriterLock offered pretty poor performance)
With this model, the Add() method is less expensive than the previous model (although Add still has to take an exclusive lock). With this model, we don't have to create copies of the list. When we enumerate the list, we enter a readlock, which does not block other readers, but does block/is blocked by writers (calls to Add). As to which model is better -- it probably depends upon how you are using the cache. I would recommend Testing and measuring.
public class Cache3<T> : IEnumerable<T>
{
private LinkedList<T> InternalCache = new LinkedList<T>();
private readonly System.Threading.ReaderWriterLockSlim LockObj = new System.Threading.ReaderWriterLockSlim();
public void Add(T item)
{
this.LockObj.EnterWriteLock();
try
{
if(this.InternalCache.Count == 10)
this.InternalCache.RemoveLast();
this.InternalCache.AddFirst(item);
}
finally
{
this.LockObj.ExitWriteLock();
}
}
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
this.LockObj.EnterReadLock();
try
{
foreach(T item in this.InternalCache)
yield return item;
}
finally
{
this.LockObj.ExitReadLock();
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
this.LockObj.EnterReadLock();
try
{
foreach (T item in this.InternalCache)
yield return item;
}
finally
{
this.LockObj.ExitReadLock();
}
}
#endregion
}
you might want to read up on this technique. Read-copy-update (RCU).
Posted this same answer over at: Thread-safe cache libraries for .NET
I know your pain as I am one of the Architects of Dedoose. I have messed around with a lot of caching libraries and ended up building this one after much tribulation. The one assumption for this Cache Manager is that all collections stored by this class implement an interface to get a Guid as a "Id" property on each object. Being that this is for a RIA it includes a lot of methods for adding /updating /removing items from these collections.
Here's my CollectionCacheManager
public class CollectionCacheManager
{
private static readonly object _objLockPeek = new object();
private static readonly Dictionary<String, object> _htLocksByKey = new Dictionary<string, object>();
private static readonly Dictionary<String, CollectionCacheEntry> _htCollectionCache = new Dictionary<string, CollectionCacheEntry>();
private static DateTime _dtLastPurgeCheck;
public static List<T> FetchAndCache<T>(string sKey, Func<List<T>> fGetCollectionDelegate) where T : IUniqueIdActiveRecord
{
List<T> colItems = new List<T>();
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.Keys.Contains(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
colItems = (List<T>) objCacheEntry.Collection;
objCacheEntry.LastAccess = DateTime.Now;
}
else
{
colItems = fGetCollectionDelegate();
SaveCollection<T>(sKey, colItems);
}
}
List<T> objReturnCollection = CloneCollection<T>(colItems);
return objReturnCollection;
}
public static List<Guid> FetchAndCache(string sKey, Func<List<Guid>> fGetCollectionDelegate)
{
List<Guid> colIds = new List<Guid>();
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.Keys.Contains(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
colIds = (List<Guid>)objCacheEntry.Collection;
objCacheEntry.LastAccess = DateTime.Now;
}
else
{
colIds = fGetCollectionDelegate();
SaveCollection(sKey, colIds);
}
}
List<Guid> colReturnIds = CloneCollection(colIds);
return colReturnIds;
}
private static List<T> GetCollection<T>(string sKey) where T : IUniqueIdActiveRecord
{
List<T> objReturnCollection = null;
if (_htCollectionCache.Keys.Contains(sKey) == true)
{
CollectionCacheEntry objCacheEntry = null;
lock (GetKeyLock(sKey))
{
objCacheEntry = _htCollectionCache[sKey];
objCacheEntry.LastAccess = DateTime.Now;
}
if (objCacheEntry.Collection != null && objCacheEntry.Collection is List<T>)
{
objReturnCollection = CloneCollection<T>((List<T>)objCacheEntry.Collection);
}
}
return objReturnCollection;
}
public static void SaveCollection<T>(string sKey, List<T> colItems) where T : IUniqueIdActiveRecord
{
CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();
objCacheEntry.Key = sKey;
objCacheEntry.CacheEntry = DateTime.Now;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
objCacheEntry.Collection = CloneCollection(colItems);
lock (GetKeyLock(sKey))
{
_htCollectionCache[sKey] = objCacheEntry;
}
}
public static void SaveCollection(string sKey, List<Guid> colIDs)
{
CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();
objCacheEntry.Key = sKey;
objCacheEntry.CacheEntry = DateTime.Now;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
objCacheEntry.Collection = CloneCollection(colIDs);
lock (GetKeyLock(sKey))
{
_htCollectionCache[sKey] = objCacheEntry;
}
}
public static void UpdateCollection<T>(string sKey, List<T> colItems) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
objCacheEntry.Collection = new List<T>();
//Clone the collection before insertion to ensure it can't be touched
foreach (T objItem in colItems)
{
objCacheEntry.Collection.Add(objItem);
}
_htCollectionCache[sKey] = objCacheEntry;
}
else
{
SaveCollection<T>(sKey, colItems);
}
}
}
public static void UpdateItem<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
List<T> colItems = (List<T>)objCacheEntry.Collection;
colItems.RemoveAll(o => o.Id == objItem.Id);
colItems.Add(objItem);
objCacheEntry.Collection = colItems;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
}
}
}
public static void UpdateItems<T>(string sKey, List<T> colItemsToUpdate) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
List<T> colCachedItems = (List<T>)objCacheEntry.Collection;
foreach (T objItem in colCachedItems)
{
colCachedItems.RemoveAll(o => o.Id == objItem.Id);
colCachedItems.Add(objItem);
}
objCacheEntry.Collection = colCachedItems;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
}
}
}
public static void RemoveItemFromCollection<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
List<T> objCollection = GetCollection<T>(sKey);
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0)
{
objCollection.RemoveAll(o => o.Id == objItem.Id);
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void RemoveItemsFromCollection<T>(string sKey, List<T> colItemsToAdd) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
Boolean bCollectionChanged = false;
List<T> objCollection = GetCollection<T>(sKey);
foreach (T objItem in colItemsToAdd)
{
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0)
{
objCollection.RemoveAll(o => o.Id == objItem.Id);
bCollectionChanged = true;
}
}
if (bCollectionChanged == true)
{
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void AddItemToCollection<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
List<T> objCollection = GetCollection<T>(sKey);
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0)
{
objCollection.Add(objItem);
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void AddItemsToCollection<T>(string sKey, List<T> colItemsToAdd) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
List<T> objCollection = GetCollection<T>(sKey);
Boolean bCollectionChanged = false;
foreach (T objItem in colItemsToAdd)
{
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0)
{
objCollection.Add(objItem);
bCollectionChanged = true;
}
}
if (bCollectionChanged == true)
{
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void PurgeCollectionByMaxLastAccessInMinutes(int iMinutesSinceLastAccess)
{
DateTime dtThreshHold = DateTime.Now.AddMinutes(iMinutesSinceLastAccess * -1);
if (_dtLastPurgeCheck == null || dtThreshHold > _dtLastPurgeCheck)
{
lock (_objLockPeek)
{
CollectionCacheEntry objCacheEntry;
List<String> colKeysToRemove = new List<string>();
foreach (string sCollectionKey in _htCollectionCache.Keys)
{
objCacheEntry = _htCollectionCache[sCollectionKey];
if (objCacheEntry.LastAccess < dtThreshHold)
{
colKeysToRemove.Add(sCollectionKey);
}
}
foreach (String sKeyToRemove in colKeysToRemove)
{
_htCollectionCache.Remove(sKeyToRemove);
}
}
_dtLastPurgeCheck = DateTime.Now;
}
}
public static void ClearCollection(String sKey)
{
lock (GetKeyLock(sKey))
{
lock (_objLockPeek)
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
_htCollectionCache.Remove(sKey);
}
}
}
}
#region Helper Methods
private static object GetKeyLock(String sKey)
{
//Ensure even if hell freezes over this lock exists
if (_htLocksByKey.Keys.Contains(sKey) == false)
{
lock (_objLockPeek)
{
if (_htLocksByKey.Keys.Contains(sKey) == false)
{
_htLocksByKey[sKey] = new object();
}
}
}
return _htLocksByKey[sKey];
}
private static List<T> CloneCollection<T>(List<T> colItems) where T : IUniqueIdActiveRecord
{
List<T> objReturnCollection = new List<T>();
//Clone the list - NEVER return the internal cache list
if (colItems != null && colItems.Count > 0)
{
List<T> colCachedItems = (List<T>)colItems;
foreach (T objItem in colCachedItems)
{
objReturnCollection.Add(objItem);
}
}
return objReturnCollection;
}
private static List<Guid> CloneCollection(List<Guid> colIds)
{
List<Guid> colReturnIds = new List<Guid>();
//Clone the list - NEVER return the internal cache list
if (colIds != null && colIds.Count > 0)
{
List<Guid> colCachedItems = (List<Guid>)colIds;
foreach (Guid gId in colCachedItems)
{
colReturnIds.Add(gId);
}
}
return colReturnIds;
}
#endregion
#region Admin Functions
public static List<CollectionCacheEntry> GetAllCacheEntries()
{
return _htCollectionCache.Values.ToList();
}
public static void ClearEntireCache()
{
_htCollectionCache.Clear();
}
#endregion
}
public sealed class CollectionCacheEntry
{
public String Key;
public DateTime CacheEntry;
public DateTime LastUpdate;
public DateTime LastAccess;
public IList Collection;
}
Here is an example of how I use it:
public static class ResourceCacheController
{
#region Cached Methods
public static List<Resource> GetResourcesByProject(Guid gProjectId)
{
String sKey = GetCacheKeyProjectResources(gProjectId);
List<Resource> colItems = CollectionCacheManager.FetchAndCache<Resource>(sKey, delegate() { return ResourceAccess.GetResourcesByProject(gProjectId); });
return colItems;
}
#endregion
#region Cache Dependant Methods
public static int GetResourceCountByProject(Guid gProjectId)
{
return GetResourcesByProject(gProjectId).Count;
}
public static List<Resource> GetResourcesByIds(Guid gProjectId, List<Guid> colResourceIds)
{
if (colResourceIds == null || colResourceIds.Count == 0)
{
return null;
}
return GetResourcesByProject(gProjectId).FindAll(objRes => colResourceIds.Any(gId => objRes.Id == gId)).ToList();
}
public static Resource GetResourceById(Guid gProjectId, Guid gResourceId)
{
return GetResourcesByProject(gProjectId).SingleOrDefault(o => o.Id == gResourceId);
}
#endregion
#region Cache Keys and Clear
public static void ClearCacheProjectResources(Guid gProjectId)
{ CollectionCacheManager.ClearCollection(GetCacheKeyProjectResources(gProjectId));
}
public static string GetCacheKeyProjectResources(Guid gProjectId)
{
return string.Concat("ResourceCacheController.ProjectResources.", gProjectId.ToString());
}
#endregion
internal static void ProcessDeleteResource(Guid gProjectId, Guid gResourceId)
{
Resource objRes = GetResourceById(gProjectId, gResourceId);
if (objRes != null)
{ CollectionCacheManager.RemoveItemFromCollection(GetCacheKeyProjectResources(gProjectId), objRes);
}
}
internal static void ProcessUpdateResource(Resource objResource)
{
CollectionCacheManager.UpdateItem(GetCacheKeyProjectResources(objResource.Id), objResource);
}
internal static void ProcessAddResource(Guid gProjectId, Resource objResource)
{
CollectionCacheManager.AddItemToCollection(GetCacheKeyProjectResources(gProjectId), objResource);
}
}
Here's the Interface in question:
public interface IUniqueIdActiveRecord
{
Guid Id { get; set; }
}
Hope this helps, I've been through hell and back a few times to finally arrive at this as the solution, and for us It's been a godsend, but I cannot guarantee that it's perfect, only that we haven't found an issue yet.