I recently had to write a small Flickr API. I know many .Net API for Flickr already exist but I needed one for a Silverlight application. Whatever, it's only about building some querystrings so I did it by myself. It's been an opportunity to think again about a classical question: how to pass parameters to a method ?
Imagine you have a generic method to call some Flickr functions.
public void CallFlickrMethod(string methodName, ? parameters, DownloadStringCompletedEventHandler asyncResult)
In that case, I would like to pass a collection of parameters, each parameter being a key+value structure.
The .Net framework (Silverlight too) offers a KeyValuePair<TKey, TValue> structure that we could use here.
We could imagine a method like:
public void CallFlickrMethod(string methodName, KeyValuePair<string, string>[] parameters, DownloadStringCompletedEventHandler asyncResult) Call: CallFlickrMethod("mymethod", new KeyValuePair[] { new KeyValuePair<string, string>("param1", "value1"), new KeyValuePair<string, string>("param2", "value2") }, completedHandler);
public void CallFlickrMethod(string methodName, DownloadStringCompletedEventHandler asyncResult, params KeyValuePair<string, string>[] parameters) Call: CallFlickrMethod("mymethod", completedHandler, new KeyValuePair<string, string>("param1", "value1"), new KeyValuePair<string, string>("param2", "value2"));
In that case, parameters names are unique so we are very close to have a dictionary.We could imagine:
public void CallFlickrMethod(string methodName, DownloadStringCompletedEventHandler asyncResult, Dictionary<string, string> parameters) Call: var parameters = new Dictionary<string, string>(); parameters.Add("param1", "value1"); parameters.Add("param2", "value2"); CallFlickrMethod("mymethod", completedHandler, parameters);
Then I thought about using anonymous methods. This is only working because keys are strings. In a class, properties names are unique. Properties definitions are stored in the type definition itself and the properties values are stored in the instance of the class. So we could imagine using an object as a kind of readonly dictionary (keys are fixed).
Imagine we change our method to just:
public void CallFlickrMethod(string methodName, DownloadStringCompletedEventHandler asyncResult, object parameters)
CallFlickrMethod("mymethod", completedHandler, new { param1 = "value1", param2 = "value2"});
public void CallFlickrMethod(string methodName, object parameters, DownloadStringCompletedEventHandler asyncResult) { string url = string.Format("http://api.flickr.com/services/rest/?method={0}&api_key={1}", methodName, apiKey); var q = from prop in parameters.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) select string.Format("&{0}={1}", prop.Name, prop.GetValue(parameters, null).ToString()); url += string.Join("", q.ToArray()); // Or for linq addicts :p // url += q.Aggregate(new StringBuilder(), (sb, value) => sb.Append(value)).ToString(); WebClient webClient = new WebClient(); webClient.DownloadStringCompleted += asyncResult; webClient.DownloadStringAsync(new Uri(url)); }
We could now think about generalizing the use of an object as a readonly dictionary.
What I propose is to offer a way to wrap an object in a generic class implementing IDictionary<TKey, TValue>. IDictionary is quite long to implement but here is an abstract of the important methods.
public class ObjectDictionary<T> : IDictionary<string, object> { public ObjectDictionary(T instance) { this.instance = instance; } private T instance; #region IDictionary<string,object> Members public bool ContainsKey(string key) { return typeof(T).GetProperty(key) != null; } public ICollection<string> Keys { get { return typeof(T).GetProperties() .Select(p => p.Name).ToArray(); } } public bool TryGetValue(string key, out object value) { var p = typeof(T).GetProperty(key); if (p == null) { value = null; return false; } else { value = p.GetValue(instance, null); return true; } } public ICollection<object> Values { get { return typeof(T).GetProperties() .Select(p => p.GetValue(instance, null)).ToArray(); } } public object this[string key] { get { object result = null; if (TryGetValue(key, out result)) return result; else throw new Exception("Key not found"); } set { object result = null; if (TryGetValue(key, out result)) result = value; else throw new Exception("Key not found"); } } ... #endregion #region ICollection<KeyValuePair<string,object>> Members public int Count { get { return typeof(T).GetProperties().Length; } } public bool IsReadOnly { get { return true; } } ... #endregion #region IEnumerable<KeyValuePair<string,object>> Members public IEnumerator<KeyValuePair<string, object>> GetEnumerator() { var q = from p in typeof(T).GetProperties() select new KeyValuePair<string, object>(p.Name, p.GetValue(instance, null)); return q.GetEnumerator(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion }
ObjectDictionary<Customer> c = new ObjectDictionary<Customer>( new Customer { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu" }); Console.WriteLine(c["CompanyName"]);
public static class ObjectDictionaryExtensions { public static ObjectDictionary<T> AsDictionary<T>(this T instance) { return new ObjectDictionary<T>(instance); } } Use: var cust = new Customer { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu" }; var c = cust.AsDictionary(); Console.WriteLine(c["CompanyName"]);
public class ObjectDictionary<T> : IDictionary<string, object> { public ObjectDictionary(T instance) { this.instance = instance; } public static implicit operator T (ObjectDictionary<T> source) { return source.instance; } public static implicit operator ObjectDictionary<T> (T source) { return source.AsDictionary(); } ... } Use: ObjectDictionary<Customer> c = new Customer { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu"};
var cust = new { ID="1", CompanyName="Microsoft"}; var c = c.AsDictionary(); var id = c["ID"];
var c = new ObjectDictionary<?>(new { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu"});
public class ObjectDictionary { public static ObjectDictionary<T> Create<T>(T instance) { return new ObjectDictionary<T>(instance); } } Use: var c = ObjectDictionary.Create(new { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu" }); foreach (var prop in c.Keys) Console.WriteLine(c[prop]); You can find the source code for VS2008 attached to this post.