When you deal with very dynamic dictionaries, you have always to check if the data exists before accessing it. This can blow up your code. With the dynamic data type (.NET 4.0) you can do this a little bit more elegant.
Example:
Here are my data classes. You see that the Person class has a dictionary which can take every object as a value.
public class Address { public string Street { get; set; } public string City { get; set; } public int ZIP { get; set; } } public class Person { public IDictionary<string, object> Data { get; set; } public Person() { this.Data = new Dictionary<string, object>(); } }
When you want to get data out of the dictionary you have to do following:
// Check if key exists. Then read and cast the value. if(pers.Data.ContainsKey("Something")) var val = (int)pers.Data["Something"];
With dynamic data type it could be like this.
if(dynPers.HasSomething) var val = dynPers.Something;
None of this code is more or less secure. But the approach with the dynamic data type looks better. 🙂
How it works:
First you have to create a class which derives from DynamicObject. I added to this (abstract) class a generic type “T”. So the code will be usable for other dynamic classes like “Person” too.
public abstract class DynamicHelper<T> : DynamicObject { public T Data { get; private set; } public Func<T, string, bool> Exists { get; private set; } public Func<T, string, object> GetData { get; private set; } public DynamicHelper(T _dynamicData, Func<T, string, bool> _exists, Func_getData) { this.Data = _dynamicData; this.Exists = _exists; this.GetData = _getData; } public override bool TryGetMember(GetMemberBinder binder, out object result) { result = false; if (this.Data == null) return false; if (binder.Name.ToLower().StartsWith("has")) { if (this.Exists(this.Data, binder.Name.Substring(3))) { result = true; return true; } else { result = false; return true; } } else if (this.Exists(this.Data, binder.Name)) { result = this.GetData(this.Data, binder.Name); return true; } result = null; return false; } }
Then you have to create a conrecte class, which derives from DynamicHelper. This class here is for my dynamic/flexible type “Person”.
public class DynamicPerson : DynamicHelper{ public DynamicPerson(Person _data) : base(_data, new Func<Person, string, bool>((p, b) => { return p.Data.Count(x => x.Key.ToLower() == b.ToLower()) > 0; }), new Func<Person,string,object>((p, b) => { return p.Data.First(x => x.Key.ToLower() == b.ToLower()).Value; })) { } }
The constructor passes to the base constructor three parameters:
_data | Data object. In this example an object of “Person” class. |
_exists | A Func<> which checks if the data object contains a specific key/property. |
_getData | A Func<> which retrieves a value out of the the data object. |
This is it. Here is a short example:
class Program { static void Main(string[] args) { Stopwatch watch = new Stopwatch(); // Initialize data Person pers = new Person(); pers.Data.Add("HelloWorld", "Hello !!!"); pers.Data.Add("PersonData", new Address() { City = "Belgrade", Street = "Street 123a", ZIP = 1332 }); Console.WriteLine("------------"); // Start watch watch.Start(); // Create dynamic object dynamic dynamicPerson = new DynamicPerson(pers); // Get helloworld if (dynamicPerson.HasHelloWorld) Console.WriteLine(dynamicPerson.HelloWorld); Console.WriteLine(); // Get data if (dynamicPerson.HasPersonData) { Console.WriteLine("Street: " + dynamicPerson.PersonData.Street); Console.WriteLine("ZIP: " + dynamicPerson.PersonData.ZIP.ToString()); Console.WriteLine("City: " + dynamicPerson.PersonData.City); } // Stop watch watch.Stop(); // Time Console.WriteLine("Elapsed ms: " + watch.ElapsedMilliseconds.ToString()); Console.WriteLine(); Console.WriteLine("------------"); Console.WriteLine(); // Restart watch and do the same but without dynamic data type watch.Restart(); if (pers.Data.ContainsKey("HelloWorld")) Console.WriteLine(pers.Data["HelloWorld"].ToString()); Console.WriteLine(); if (pers.Data.ContainsKey("PersonData")) { Console.WriteLine("Street: " + ((Address)pers.Data["PersonData"]).Street); Console.WriteLine("ZIP: " + ((Address)pers.Data["PersonData"]).ZIP.ToString()); Console.WriteLine("City: " + ((Address)pers.Data["PersonData"]).City); } watch.Stop(); Console.WriteLine("Elapsed ms: " + watch.ElapsedMilliseconds.ToString()); Console.ReadLine(); } }
You see that the code above first reads the data out of the “pers” object the dynamic way. Then it reads the same data but the “normal” way with contains-check and casting.
The output is the same:
Looks good or not? The code is a bit clearer when using the dynamic extension. But the performance is poor.
The dynamic extension needs !!187ms!! for reading the data. And the other piece of code is a lot faster.
So, use the dynamic way only when speed isn’t very important for you… or for your customer. 😉