Welcome to MSDN Blogs Sign in | Join | Help

Mitsu's blog

Discussing topics related to .Net, WPF, C# and Linq
Thinking about new C# method prototypes: object as dictionary

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)

The goal here is to finally build a querystring like: http://api.flickr.com/services/rest/?method=mymethod&param1=value1&param2=value2...

 

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);
You may also know the 'params' keyword that allows you to pass parameters separated by commas instead of having to build an array:

 

 

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"));
You can notice that the params parameter must be the last one of the method prototype. This syntax is shorter but still a little bit heavy to write because we have to create many KeyValuePair structures.

 

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);

The method is simpler but the call is still heavy because of the dictionary creation.

 

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)

Then we could use new C#3 anonymous types to write:

 

 

CallFlickrMethod("mymethod", completedHandler, new { param1 = "value1", param2 = "value2"});
This syntax is of course very short and also very easy to use.
The following code is analyzing the 'parameters' object using reflection to retrieve the equivalent of a collection of KeyValuePairs.

 

 

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)); }

Let's say this is the end of part I.

 

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 }
Then you can write things like:

 

 

ObjectDictionary<Customer> c = new ObjectDictionary<Customer>( new Customer { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu" }); Console.WriteLine(c["CompanyName"]);
With an extension method ?

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"]);

With an implicit conversion ?

 

 

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"};

Of course all these features are more funny to use with anonymous types:

 

var cust = new { ID="1", CompanyName="Microsoft"}; var c = c.AsDictionary(); var id = c["ID"];

Let's see one last point. In the case of an anonymous type, we can't use the following syntax:
var c = new ObjectDictionary<?>(new { ID = "1", CompanyName = "Microsoft", ContactName = "Mitsu"});

This is a classical problem where a regular constructor can not infer T from the parameters. If you want to do it, you have to create a static generic method to create your instance. Then the inference will work fine and make the use of an anonymous type possible.
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.
Posted: Wednesday, June 18, 2008 7:27 PM by mitsu
Filed under: , , ,

Attachment(s): ObjectAsDictionary.zip

Comments

Matt Hamilton said:

Mitsu,

This post reminds me of a recent post by Paul Stovell:

http://www.paulstovell.com/blog/snippet-ruby-like-hashes-in-c

... in which he discusses using lambda expressions as hashes similar to what you've done here with anonymous classes.

Matt

# June 18, 2008 7:03 PM

mitsu said:

Thanks for the link Matt,

When I have time I should read more about Ruby...

# June 20, 2008 5:36 AM

webflo said:

What about performance ? Because this solution use reflection, isn't it ? So, call "GetProperties" or "GetProperty" methods has a cost, right ? or i'm completely wrong? This is just a question because i'm curious and i want to learn more about c# 3.0 :)

# August 19, 2008 4:11 AM

mitsu said:

You are right. That's why it's not for general use but to respond to some scenario where you generaly use reflection.

For example, an anonymous type can't be used outside of the method that is declaring it. But you can still choose to return your anonymous type as an object and then use reflection for the callers.

It can also be used directly with some methods that are waiting dictionaries.(ex: build a Json object).

# August 19, 2008 9:42 AM

AK said:

Thanks for the inspiring article. It encouraged me to go ahead and implement a similar mechanism for passing anonymous types as generic parameters for methods.

Performance incurs a slight over head due to the use of reflection. As an example a method call like:

public static string[] FindUsersByProfileCriteria(object criteria)

where criteria is an passed from the caller as an anonymous type. Has an average execution time of about  0.00883 seconds; with only 6% of that time because of reflection.

I'd say I can handle that little overhead for the added benefit.

Thanks again.

# November 18, 2008 6:51 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

  
Enter Code Here: Required

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Page view tracker