I have a dictionary of strings that i want the user to be able to add/remove info from then store it for them so it they can access it the next time the program restarts
I am unclear on how i can store a dictionary as a setting. I see that under system.collections.special there is a thing called a stringdictionary but ive read that SD are outdated and shouldn't be used.
also in the future i may have need to store a dictionary that is not strings only (int string)
how would you store a dictionary in the settings file for a .net application?
You can use this class derived from StringDictionary. To be useful for application settings it implements IXmlSerializable.
Or you can use similar approach to implement your own XmlSerializable class.
public class SerializableStringDictionary : System.Collections.Specialized.StringDictionary, System.Xml.Serialization.IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
while (reader.Read() &&
!(reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.LocalName == this.GetType().Name))
{
var name = reader["Name"];
if (name == null)
throw new FormatException();
var value = reader["Value"];
this[name] = value;
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (System.Collections.DictionaryEntry entry in this)
{
writer.WriteStartElement("Pair");
writer.WriteAttributeString("Name", (string)entry.Key);
writer.WriteAttributeString("Value", (string)entry.Value);
writer.WriteEndElement();
}
}
}
Resulting XML fragment will look similar to:
...
<setting name="PluginSettings" serializeAs="Xml">
<value>
<SerializableStringDictionary>
<Pair Name="property1" Value="True" />
<Pair Name="property2" Value="05/01/2011 0:00:00" />
</SerializableStringDictionary>
</value>
</setting>
...
The simplest answer would be to use a row & column delimiter to convert your dictionary to a single string. Then you just need to store 1 string in the settings file.
If you don't need to use the settings designer or edit your settings with a text editor, you can create a simple class that derives from ApplicationSettingsBase:
namespace MyNamespace
{
using System.Collections.Generic;
using System.Configuration;
/// <summary>
/// Persistent store for my parameters.
/// </summary>
public class MySettings : ApplicationSettingsBase
{
/// <summary>
/// The instance lock.
/// </summary>
private static readonly object InstanceLock = new object();
/// <summary>
/// The instance.
/// </summary>
private static MySettings instance;
/// <summary>
/// Prevents a default instance of the <see cref="MySettings"/> class
/// from being created.
/// </summary>
private MySettings()
{
// don't need to do anything
}
/// <summary>
/// Gets the singleton.
/// </summary>
public static MySettings Instance
{
get
{
lock (InstanceLock)
{
if (instance == null)
{
instance = new MySettings();
}
}
return instance;
}
}
/// <summary>
/// Gets or sets the parameters.
/// </summary>
[UserScopedSetting]
[SettingsSerializeAs(SettingsSerializeAs.Binary)]
public Dictionary<string, string> Parameters
{
get
{
return (Dictionary<string, string>)this["Parameters"];
}
set
{
this["Parameters"] = value;
}
}
}
}
The real trick is the [SettingsSerializeAs(SettingsSerializeAs.Binary)] attribute. Most (all?) classes can get serialized this way where SettingsSerializeAs.String or SettingsSerializeAs.Xml wont work for a Dictionary.
Use this in your code as you would normal settings:
// this code untested...
MySettings.Instance.Parameters["foo"] = "bar";
MySettings.Instance.Parameters.Save();
MySettings.Instance.Parameters.Reload();
string bar;
if (!MySettings.Instance.Parameters.TryGetValue("foo", out bar))
{
throw new Exception("Foobar");
}
If you want the Dictionary to serialize into something user editable, you must derive from Dictionary and play with TypeConverter (see Using Custom Classes with Application Settings).
Other than doing something like David's suggests, I would look into alternate storage for the Dictionary. Ultimately the Settings object serializes to disk.
Have you considered using XML to store your dictionary? That would provide a certain amount of extensibility if in the future you decide you want to be able to store other types of dictionaries. You might do something like:
<dictionary>
<entry key="myKey">
[whatever data you like]
</entry>
</dictionary>
Might be overkill, but you'd also be prepared in the case that you wanted to store more complex data, like custom objects.
You can also use a System.Collections.Specialized.StringCollection by putting key on even index and values on odd index.
/// <summary>
/// Emulate a Dictionary (Serialization pb)
/// </summary>
private static string getValue(System.Collections.Specialized.StringCollection list, string key)
{
for (int i = 0; i * 2 < list.Count; i++)
{
if (list[i] == key)
{
return list[i + 1];
}
}
return null;
}
/// <summary>
/// Emulate a Dictionary (Serialization pb)
/// </summary>
private static void setValue(System.Collections.Specialized.StringCollection list, string key, string value)
{
for (int i = 0; i * 2 < list.Count; i++)
{
if (list[i] == key)
{
list[i + 1] = value;
return;
}
}
list.Add(key);
list.Add(value);
}
You could create a custom class that exposes a Dictionary as a public property. Then you can specify this custom type as the type for your setting.
Edit:
I have just read that, for some reason, a generic dictionary cannot be XML-serialized, so my solution will probably not work (I haven't tested it though...). That's strange, because a generic list can be serialized without any problem.
You could still create a custom class that can be set as a user setting, but you will need to have a list exposed as a property instead of a dictionary.
Edit: This will return a Hashtable (for whatever reason, despite being a 'DictionarySectionHandler'). However, being that Hashtables and Dictionaries are so similar, it shouldn't be a large issue (though I realize Dictionaries are newer, parameterized, etc; I would have preferred dicitonaries myself, but this is what .NET gives us).
The best answer I just found for this is here. It returns a typesafe collection witout any muddling in code to transform it, and you create an obvious (and simple) collection in your .config file. I'm using this and it's quite straight forward for any future programmer (including yourself). It allows for stronger typing and more flexibility, without any overly-complicated and unnecessary parsing.
You can store a StringCollection. It is similar to this solution.
I made 2 extension methods to convert between StringCollection and a Dictionary. This is the easiest way I could think of.
public static class Extender
{
public static Dictionary<string, string> ToDictionary(this StringCollection sc)
{
if (sc.Count % 2 != 0) throw new InvalidDataException("Broken dictionary");
var dic = new Dictionary<string, string>();
for (var i = 0; i < sc.Count; i += 2)
{
dic.Add(sc[i], sc[i + 1]);
}
return dic;
}
public static StringCollection ToStringCollection(this Dictionary<string, string> dic)
{
var sc = new StringCollection();
foreach (var d in dic)
{
sc.Add(d.Key);
sc.Add(d.Value);
}
return sc;
}
}
class Program
{
static void Main(string[] args)
{
//var sc = new StringCollection();
//sc.Add("Key01");
//sc.Add("Val01");
//sc.Add("Key02");
//sc.Add("Val02");
var sc = Settings.Default.SC;
var dic = sc.ToDictionary();
var sc2 = dic.ToStringCollection();
Settings.Default.SC = sc2;
Settings.Default.Save();
}
}
Related
I use a object based on Dictionary in my application.
When debugging (and only when i inspect the Dictionary) i would like to wiew the content of the dictionary but sorted on key.
I know that i could use SortedDictionary instead of Dictionary but the performance is poor compare to Dictionary and i don't want to affect performance.
I don't want to have a "#if debug" condition either.
Is it possible ?
You can specify a DebuggerTypeProxyAttribute() on your class that is used when/if you debug. This proxy has to sort the data for you.
Article: Enhancing Debugging with the Debugger Display Attributes
Example using a (senseless) child of a Dictionary<string,int>:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
internal class Program
{
/// <summary>
/// Derives from a Dictionary that is not sorted
/// </summary>
[DebuggerTypeProxy(typeof(DictDebugView))]
public class MyDictionary : Dictionary<string, int>
{
/// <summary>
/// Prepares unsorted dummy data
/// </summary>
public void PopulateDemoData()
{
foreach (char t in "GERZWIQSFHIWE")
this[new string(t, t / 10)] = t;
}
/// <summary>
/// Is used as proxy for display
/// </summary>
internal class DictDebugView
{
private readonly SortedDictionary<string, int> sorted;
public DictDebugView(Dictionary<string, int> data)
=> sorted = new SortedDictionary<string, int>(data);
/// <summary>
/// Create the displayed KeyValuePairs
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public IList<KeyValuePair<string,int>> Keys
{
get => sorted.Select(kvp => kvp).ToList();
}
}
}
public static MyDictionary MyProp { get; } = new MyDictionary();
public static void Main(string[] args)
{
var md = new MyDictionary();
md.PopulateDemoData();
var k = new Dictionary<string,int>(md);
Console.ReadLine();
}
}
If you place a breakpoint and debug you get a sorted output for your class with internal DebuggerTypeProxy:
and unsorted output for the "normal" dictionary that does not use any proxy to display its data:
Using this in watch :
new SortedDictionary<string,object>(dictionary)
or a method Debug with that return the sortedDictionary
Try to put following line of code into watch during debugging you will find desire result.
l_oDictionary.OrderBy(key => key.Key);
I'm green on C# and .NET. I'm trying to tie other values to enumerations. I'm also trying to use a generic function to retrieve these values (from http://omegacoder.com/?p=28). The generic function does not work for me. It just returns the name of the Attribute type. I defined a custom Attribute, but I don't know how to get at the "Num" Property of it.
Basically, if I have a CRC polynomial enumeration value of Crc32ieeeNormal (1), I want to be able to retrieve the polynomial itself. These must be separate numbers, as the enumeration must be a single byte indicator.
I'm sure one could write separate structures, using the same numbers, etc., and that's what I'll do if I have to, but I'd like it all tied together.
using System;
using System.Linq;
namespace csAttributes
{
using System.Reflection; // to retrieve attribute values
class Program
{
static void Main(string[] args)
{
// I know this isn't right, but can't figure out how to get the attribute value....
CrcPolynomialTypesEnum crc = CrcPolynomialTypesEnum.Crc32ieeeNormal;
Console.WriteLine(Utility.ExtractAttribute<U64Attribute, CrcPolynomialTypesEnum>(crc));
Console.WriteLine(crc.ExtractAttribute<U64Attribute, CrcPolynomialTypesEnum>());
Console.ReadKey();
}
public enum CrcPolynomialTypesEnum : byte
{
// These must be explicitly defined as they are values used as indicators in a file.
[U64(0)]
Crc32InvalidOrNotSet = 0x00,
[U64(0x04C11DB7)]
Crc32ieeeNormal = 0x01,
[U64(0xEDB88320)]
Crc32ieeeReversed = 0x02,
[U64(0x1EDC6F41)]
Crc32CastagnoliNormal = 0x0A,
[U64(0x82F63B78)]
Crc32CastagnoliReversed = 0x0B
}
}
public static class Utility {
// from http://omegacoder.com/?p=28, 8/11/2017
/// <summary>
/// If an enum has a custom attrbute, this will returrn that attribute or null.
/// </summary>
/// <typeparam name="TCustomAttr">The type of the <code class="backtick">custom attribute</code> to extract from the enum.</typeparam>
/// <typeparam name="TEnumItance">The enum currently being viewed..</typeparam>
/// <param name="instance">The instance.</param>
/// <returns>The custom attribute (TCustomAttr) or null</returns>
public static TCustomAttr ExtractAttribute<TCustomAttr, TEnum>(this TEnum instance)
{
if (instance != null)
{
try
{
FieldInfo fieldInfo = instance.GetType()
.GetField(instance.ToString());
var attributes = fieldInfo.GetCustomAttributes(typeof(TCustomAttr), false)
.ToList();
if (attributes.Any())
return (TCustomAttr)attributes[0];
}
catch (Exception)
{
}
}
return default(TCustomAttr);
}
}
[AttributeUsage(AttributeTargets.Field, Inherited = true)]
public class U64Attribute : Attribute
{
private UInt64 n;
public U64Attribute(UInt64 num)
{
n = num;
}
public UInt64 Num
{
get
{
return n;
}
}
}
}
Edit: Thank you, Kevin. Funny thing is, the "Num" property wasn't available earlier, but now it is. Must have been something I didn't have right earlier.
Edit 2: Using a generic here may be pointless, and a more specific function to retrieve the value seems to be better for this application.
Hopefully I read all the code right. What it looks like is that you are getting the class U64Attribute and trying to write it instead of the .Num...that should be an easy change, just use .Num appended to the ExtractAttribute call:
Console.WriteLine(Utility.ExtractAttribute<U64Attribute, CrcPolynomialTypesEnum>(crc).Num);
I am trying to access some specific variable that are only available in a child class. But the problem is that I recieve the parent of this class by parameter. Even with casting I can't seem to be able to access the members. Can it be done?
public class ENUMTranslator : ITranslate<RedisData>
{
public string Translate(RedisData message)
{
string bitMask = message.AssociatedParam.ParamDictionary["Bitmask"];
var enumerations = (EnumParams)message.AssociatedParam.EnumDictionary
}
}
The thing is that the data is not in message itself but inside AssociatedParam Which is the parent class of EnumParams.
The EnumDictionary is what I am trying to access that should be in EnumParams, but I just can't access it.
EDIT : Here is the EnumParam class.
message.AssociatedParams
is a GAPParam
public class EnumParams : GAPParam
{
#region Class Members
/// <summary>
/// Dictionary for the enums linking name with hex value
/// </summary>
private Dictionary<string, string> _enumDictionary;
#endregion // Class Members
#region Properties
/// <summary>
/// Dictionary for the enums linking name with hex value
/// </summary>
public Dictionary<string, string> EnumDictionary
{
get { return _enumDictionary; }
set { _enumDictionary = value; }
}
#endregion // Properties
#region Constructor
/// <summary>
/// Initialise the dictionaries
/// </summary>
public EnumParams()
{
_enumDictionary = new Dictionary<string, string>();
}
#endregion // Constructor
}
I cannot see it with intellisense and it would not compile either.
Well you could cast message.AssociatedParam to an EnumParams:
var enumerations = ((EnumParams)message.AssociatedParam).EnumDictionary
but if message.AssociatedParam is not castable to an EnumParams then it will fail at runtime. Some way to mitigate the risk:
should EnumDictionary be on GAPParam instead? Even if it's virtual or abstract?
should message.AssociatedParam be an EnumDictionary instead of a GAPParam?
do a check before casting to make sure message.AssociatedParam is an EnumParams - but then what do you do if its not?
Currently I have following list in my code.
private List<myClass> mylist;
Can I replace it with
private LinkedList<myClass> mylist;
?
Goal in mind is that that when size of mylist extends a certain size (lets say 10) I remove the oldest entry from list and add the newest one and so on. Apparantly LinkedList has following methods which I can use (they are not present in List)
mylist.RemoveFirst();
mylist.AddLast(..);
So my q is if I change from List to LinkedList will there be any loss of functionality? I mean other things in my code dependent on List will be affected or is it like this that what ever you are doing with List you can also do it with LInkedList ?
Secondly, or is it not necessary to change to LinkedList. what I am aiming to achieve I can do it with List ?
(I was thinking in these lines if I use List to achieve my goal
if (mylist.Count >= maxNumEntries)
{
list.RemoveAt(0);
}
list.Add(..);
Thanks
My advice is that no need to change List to LinkedList. It may or may not create confusion depending on your code. Moreover, you can make these simple methods, using LINQ quite easily.
I am giving one example of such method below: -
You can make custom RemoveFirst method:-
List<T> RemoveFirst(List<T> paramList)
{
List<T> tempList = paramList.Skip(1).ToList() ;
return tempList ;
}
and similarly you can make other methods to manipulate your List<T>.
Usage
List<int> myList = new List<int> () ;
// add some items to list
// now remove the first item.
myList = RemoveFirst(myList) ;
Selection of DataStructure
List<T> and LinkedList<T> are different not only in the custom methods they offer, but also in the implementation.
for eg
LinkedList<T> provides slower access to elements, as compared to List<T>.
I am sure there can be many advantages in favor of LinkedList<T> also, but good programmers select data structure on the basis of properties they offer.
In your case, a Circular queue can also be useful.
So please select the data-structure not on the basis of the methods they offer, but on the properties you think will be important for your application.
You seem to want a circular buffer (also known as a ring buffer).
You might be best just writing a custom class to implement it, such as this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace Demo
{
public class CircularBuffer<T>: IEnumerable<T>
{
/// <summary>Constructor.</summary>
/// <param name="capacity">The maximum capacity of the buffer.</param>
public CircularBuffer(int capacity)
{
// The reason for this +1 is to simplify the logic - we can use "front == back" to indicate an empty buffer.
_buffer = new T[capacity+1];
}
/// <summary>The buffer capacity.</summary>
public int Capacity
{
get
{
return _buffer.Length - 1;
}
}
/// <summary>The number of elements currently stored in the buffer.</summary>
public int Count
{
get
{
int result = _back - _front;
if (result < 0)
result += _buffer.Length;
return result;
}
}
/// <summary>Is the buffer empty?</summary>
public bool IsEmpty
{
get
{
return this.Count == 0;
}
}
/// <summary>Is the buffer full? (i.e. has it reached its capacity?)</summary>
public bool IsFull
{
get
{
return nextSlot(_back) == _front;
}
}
/// <summary>Empties the buffer.</summary>
public void Empty()
{
_front = _back = 0;
Array.Clear(_buffer, 0, _buffer.Length); // Destroy any old references so they can be GCed.
}
/// <summary>Add an element to the buffer, overwriting the oldest element if the buffer is full.</summary>
/// <param name="newItem">The element to add.</param>
public void Add(T newItem)
{
_buffer[_back] = newItem;
_back = nextSlot(_back);
if (_back == _front) // Buffer is full?
{
_front = nextSlot(_front); // Bump the front, overwriting the current front.
_buffer[_back] = default(T); // Remove the old front value.
}
}
/// <summary>
/// The typesafe enumerator. Elements are returned in oldest to newest order.
/// This is not threadsafe, so if you are enumerating the buffer while another thread is changing it you will run
/// into threading problems. Therefore you must use your own locking scheme to avoid the problem.
/// </summary>
public IEnumerator<T> GetEnumerator()
{
for (int i = _front; i != _back; i = nextSlot(i))
yield return _buffer[i];
}
/// <summary>The non-typesafe enumerator.</summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator(); // Implement in terms of the typesafe enumerator.
}
/// <summary>Calculates the index of the slot following the specified one, wrapping if necessary.</summary>
private int nextSlot(int slot)
{
return (slot + 1) % _buffer.Length;
}
/// <summary>
/// The index of the element at the front of the buffer.
/// If this equals _back, the buffer is empty.
/// </summary>
private int _front;
/// <summary>
/// The index of the first element BEYOND the last used element of the buffer.
/// Therefore this indicates where the next added element will go.
/// </summary>
private int _back;
/// <summary>The underlying buffer. This has a length one greater than the actual capacity.</summary>
private readonly T[] _buffer;
}
internal class Program
{
private void run()
{
CircularBuffer<int> buffer = new CircularBuffer<int>(10);
for (int i = 0; i < 20; ++i)
buffer.Add(i);
foreach (int n in buffer)
Console.WriteLine(n); // Prints 10..19
}
private static void Main()
{
new Program().run();
}
}
}
Does anyone have any advice on which method is better when caching data in a C# ASP.net application?
I am currently using a combination of two approaches, with some data (List, dictionaries, the usual domain-specific information) being put directly into the cache and boxed when needed, and some data being kept inside a globaldata class, and retrieved through that class (i.e. the GlobalData class is cached, and it's properties are the actual data).
Is either approach preferable?
I get the feeling that caching each item separately would be more sensible from a concurrency point of view, however it creates a lot more work in the long run with more functions that purely deal with getting data out of a cache location in a Utility class.
Suggestions would be appreciated.
Generally the cache's performance is so much better than the underlying source (e.g. a DB) that the performance of the cache is not a problem. The main goal is rather to get as high cache-hit ratio as possible (unless you are developing at really large scale because then it pays off to optimize the cache as well).
To achieve this I usually try to make it as straight forward as possible for the developer to use cache (so that we don't miss any chances of cache-hits just because the developer is too lazy to use the cache). In some projects we've use a modified version of a CacheHandler available in Microsoft's Enterprise Library.
With CacheHandler (which uses Policy Injection) you can easily make a method "cacheable" by just adding an attribute to it. For instance this:
[CacheHandler(0, 30, 0)]
public Object GetData(Object input)
{
}
would make all calls to that method cached for 30 minutes. All invocations gets a unique cache-key based on the input data and method name so if you call the method twice with different input it doesn't get cached but if you call it >1 times within the timout interval with the same input then the method only gets executed once.
Our modified version looks like this:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Middleware.Cache
{
/// <summary>
/// An <see cref="ICallHandler"/> that implements caching of the return values of
/// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request.
/// </summary>
[ConfigurationElementType(typeof (CacheHandler)), Synchronization]
public class CacheHandler : ICallHandler
{
/// <summary>
/// The default expiration time for the cached entries: 5 minutes
/// </summary>
public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0);
private readonly object cachedData;
private readonly DefaultCacheKeyGenerator keyGenerator;
private readonly bool storeOnlyForThisRequest = true;
private TimeSpan expirationTime;
private GetNextHandlerDelegate getNext;
private IMethodInvocation input;
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest)
{
keyGenerator = new DefaultCacheKeyGenerator();
this.expirationTime = expirationTime;
this.storeOnlyForThisRequest = storeOnlyForThisRequest;
}
/// <summary>
/// This constructor is used when we wrap cached data in a CacheHandler so that
/// we can reload the object after it has been removed from the cache.
/// </summary>
/// <param name="expirationTime"></param>
/// <param name="storeOnlyForThisRequest"></param>
/// <param name="input"></param>
/// <param name="getNext"></param>
/// <param name="cachedData"></param>
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest,
IMethodInvocation input, GetNextHandlerDelegate getNext,
object cachedData)
: this(expirationTime, storeOnlyForThisRequest)
{
this.input = input;
this.getNext = getNext;
this.cachedData = cachedData;
}
/// <summary>
/// Gets or sets the expiration time for cache data.
/// </summary>
/// <value>The expiration time.</value>
public TimeSpan ExpirationTime
{
get { return expirationTime; }
set { expirationTime = value; }
}
#region ICallHandler Members
/// <summary>
/// Implements the caching behavior of this handler.
/// </summary>
/// <param name="input"><see cref="IMethodInvocation"/> object describing the current call.</param>
/// <param name="getNext">delegate used to get the next handler in the current pipeline.</param>
/// <returns>Return value from target method, or cached result if previous inputs have been seen.</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
lock (input.MethodBase)
{
this.input = input;
this.getNext = getNext;
return loadUsingCache();
}
}
public int Order
{
get { return 0; }
set { }
}
#endregion
private IMethodReturn loadUsingCache()
{
//We need to synchronize calls to the CacheHandler on method level
//to prevent duplicate calls to methods that could be cached.
lock (input.MethodBase)
{
if (TargetMethodReturnsVoid(input) || HttpContext.Current == null)
{
return getNext()(input, getNext);
}
var inputs = new object[input.Inputs.Count];
for (int i = 0; i < inputs.Length; ++i)
{
inputs[i] = input.Inputs[i];
}
string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
object cachedResult = getCachedResult(cacheKey);
if (cachedResult == null)
{
var stopWatch = Stopwatch.StartNew();
var realReturn = getNext()(input, getNext);
stopWatch.Stop();
if (realReturn.Exception == null && realReturn.ReturnValue != null)
{
AddToCache(cacheKey, realReturn.ReturnValue);
}
return realReturn;
}
var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments);
return cachedReturn;
}
}
private object getCachedResult(string cacheKey)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
if (cacheKey == null)
{
return null;
}
object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey];
var cachedValueCast = cachedValue as CacheHandler;
if (cachedValueCast != null)
{
//This is an object that is reloaded when it is being removed.
//It is therefore wrapped in a CacheHandler-object and we must
//unwrap it before returning it.
return cachedValueCast.cachedData;
}
return cachedValue;
}
private static bool TargetMethodReturnsVoid(IMethodInvocation input)
{
var targetMethod = input.MethodBase as MethodInfo;
return targetMethod != null && targetMethod.ReturnType == typeof (void);
}
private void AddToCache(string key, object valueToCache)
{
if (key == null)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
return;
}
if (!storeOnlyForThisRequest)
{
HttpRuntime.Cache.Insert(
key,
valueToCache,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
expirationTime,
CacheItemPriority.Normal, null);
}
else
{
HttpContext.Current.Items[key] = valueToCache;
}
}
}
/// <summary>
/// This interface describes classes that can be used to generate cache key strings
/// for the <see cref="CacheHandler"/>.
/// </summary>
public interface ICacheKeyGenerator
{
/// <summary>
/// Creates a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
string CreateCacheKey(MethodBase method, object[] inputs);
}
/// <summary>
/// The default <see cref="ICacheKeyGenerator"/> used by the <see cref="CacheHandler"/>.
/// </summary>
public class DefaultCacheKeyGenerator : ICacheKeyGenerator
{
private readonly LosFormatter serializer = new LosFormatter(false, "");
#region ICacheKeyGenerator Members
/// <summary>
/// Create a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
public string CreateCacheKey(MethodBase method, params object[] inputs)
{
try
{
var sb = new StringBuilder();
if (method.DeclaringType != null)
{
sb.Append(method.DeclaringType.FullName);
}
sb.Append(':');
sb.Append(method.Name);
TextWriter writer = new StringWriter(sb);
if (inputs != null)
{
foreach (var input in inputs)
{
sb.Append(':');
if (input != null)
{
//Diffrerent instances of DateTime which represents the same value
//sometimes serialize differently due to some internal variables which are different.
//We therefore serialize it using Ticks instead. instead.
var inputDateTime = input as DateTime?;
if (inputDateTime.HasValue)
{
sb.Append(inputDateTime.Value.Ticks);
}
else
{
//Serialize the input and write it to the key StringBuilder.
serializer.Serialize(writer, input);
}
}
}
}
return sb.ToString();
}
catch
{
//Something went wrong when generating the key (probably an input-value was not serializble.
//Return a null key.
return null;
}
}
#endregion
}
}
Microsoft deserves most credit for this code. We've only added stuff like caching at request level instead of across requests (more useful than you might think) and fixed some bugs (e.g. equal DateTime-objects serializing to different values).
Under what conditions do you need to invalidate your cache? Objects should be stored so that when they are invalidated repopulating the cache only requires re-caching the items that were invalidated.
For example if you have cached say a Customer object that contains the delivery details for an order along with the shopping basket. Invalidating the shopping basket because they added or removed an item would also require repopulating the delivery details unnecessarily.
(NOTE: This is an obteuse example and I'm not advocating this just trying to demonstrate the principle and my imagination is a bit off today).
Ed, I assume those lists and dictionaries contain almost static data with low chances of expiration. Then there's data that gets frequent hits but also changes more frequently, so you're caching it using the HttpRuntime cache.
Now, you should think of all that data and all of the dependencies between diferent types. If you logically find that the HttpRuntime cached data depends somehow on your GlobalData items, you should move that into the cache and set up the appropriate dependencies in there so you'll benefit of the "cascading expiration".
Even if you do use your custom caching mechanism, you'd still have to provide all the synchronization, so you won't save on that by avoiding the other.
If you need (preordered) lists of items with a really low frequency change, you can still do that by using the HttpRuntime cache. So you could just cache a dictionary and either use it to list your items or to index and access by your custom key.
How about the best (worst?) of both worlds?
Have the globaldata class manage all the cache access internally. The rest of your code can then just use globaldata, meaning that it doesn't need to be cache-aware at all.
You could change the cache implementation as/when you like just by updating globaldata, and the rest of your code won't know or care what's going on inside.
There's much more than that to consider when architecting your caching strategy. Think of your cache store as if it were your in-memory db. So carefully handle dependencies and expiration policy for each and every type stored in there. It really doesn't matter what you use for caching (system.web, other commercial solution, rolling your own...).
I'd try to centralize it though and also use some sort of a plugable architecture. Make your data consumers access it through a common API (an abstract cache that exposes it) and plug your caching layer at runtime (let's say asp.net cache).
You should really take a top down approach when caching data to avoid any kind of data integrity problems (proper dependecies like I said) and then take care of providing synchronization.