Yesterday I wrote my first lines of code using the new dynamic
type in .NET 4.0. The scenario where I found this useful is as follows:
I have a class holding several lists of values. This can be List<string>
, List<bool>
, List<int>
or really any kind of list. The way these are used, is that I add a value to one or more of these lists. Then I "synchronize" them, so that they all end up the same length (those too short are filled with a default value). And then I continue to add more values, sync again etc. The goal is that the item at any index in one of the lists are related to an item at the same index in another list. (Yes, this could probably be better solved by wrapping all this in another class, but that's not the point in this case.)
I have this construct in a couple of classes, so I wanted to make this synchronizing of the lists as generic as possible. But since the inner type of the lists might vary, this wasn't as straight forward as I first had thought. But, enter the hero of the day: dynamics :)
I wrote the following helper class that can take a collection of lists (of any type) together with a default value for each list:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Foo.utils
{
public class ListCollectionHelper
{
/// <summary>
/// Takes a collection of lists and synchronizes them so that all of the lists are the same length (matching
/// the length of the longest list present in the parameter).
///
/// It is assumed that the dynamic type in the enumerable is of the type Tuple<ICollection<T>, T>, i.e. a
/// list of tuples where Item1 is the list itself, and Item2 is the default value (to fill the list with). In
/// each tuple, the type T must be the same for the list and the default value, but between the tuples the type
/// might vary.
/// </summary>
/// <param name="listCollection">A collection of tuples with a List<T> and a default value T</param>
/// <returns>The length of the lists after the sync (length of the longest list before the sync)</returns>
public static int SyncListLength(IEnumerable<dynamic> listCollection)
{
int maxNumberOfItems = LengthOfLongestList(listCollection);
PadListsWithDefaultValue(listCollection, maxNumberOfItems);
return maxNumberOfItems;
}
private static int LengthOfLongestList(IEnumerable<dynamic> listCollection)
{
return listCollection.Aggregate(0, (current, tuple) => Math.Max(current, tuple.Item1.Count));
}
private static void PadListsWithDefaultValue(IEnumerable<dynamic> listCollection, int maxNumberOfItems)
{
foreach (dynamic tuple in listCollection)
{
FillList(tuple.Item1, tuple.Item2, maxNumberOfItems);
}
}
private static void FillList<T>(ICollection<T> list, T fillValue, int maxNumberOfItems)
{
int itemsToAdd = maxNumberOfItems - list.Count;
for (int i = 0; i < itemsToAdd; i++)
{
list.Add(fillValue);
}
}
}
}
And below is a short set of unit tests I used to verify that I ended up with the desired behaviour:
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Foo.utils;
namespace Foo.UnitTests
{
[TestClass]
public class DynamicListSync
{
private readonly List<string> stringList = new List<string>();
private readonly List<bool> boolList = new List<bool>();
private readonly List<string> stringListWithCustomDefault = new List<string>();
private readonly List<int> intList = new List<int>();
private readonly List<dynamic> listCollection = new List<dynamic>();
private const string FOO = "bar";
[TestInitialize]
public void InitTest()
{
listCollection.Add(Tuple.Create(stringList, default(String)));
listCollection.Add(Tuple.Create(boolList, default(Boolean)));
listCollection.Add(Tuple.Create(stringListWithCustomDefault, FOO));
listCollection.Add(Tuple.Create(intList, default(int)));
}
[TestMethod]
public void SyncEmptyLists()
{
Assert.AreEqual(0, ListCollectionHelper.SyncListLength(listCollection));
}
[TestMethod]
public void SyncWithOneListHavingOneItem()
{
stringList.Add("one");
Assert.AreEqual(1, ListCollectionHelper.SyncListLength(listCollection));
Assert.AreEqual("one", stringList[0]);
Assert.AreEqual(default(Boolean), boolList[0]);
Assert.AreEqual(FOO, stringListWithCustomDefault[0]);
Assert.AreEqual(default(int), intList[0]);
}
[TestMethod]
public void SyncWithAllListsHavingSomeItems()
{
stringList.Add("one");
stringList.Add("two");
stringList.Add("three");
boolList.Add(false);
boolList.Add(true);
stringListWithCustomDefault.Add("one");
Assert.AreEqual(3, ListCollectionHelper.SyncListLength(listCollection));
Assert.AreEqual("one", stringList[0]);
Assert.AreEqual("two", stringList[1]);
Assert.AreEqual("three", stringList[2]);
Assert.AreEqual(false, boolList[0]);
Assert.AreEqual(true, boolList[1]);
Assert.AreEqual(default(Boolean), boolList[2]);
Assert.AreEqual("one", stringListWithCustomDefault[0]);
Assert.AreEqual(FOO, stringListWithCustomDefault[1]);
Assert.AreEqual(FOO, stringListWithCustomDefault[2]);
Assert.AreEqual(default(int), intList[0]);
Assert.AreEqual(default(int), intList[1]);
Assert.AreEqual(default(int), intList[2]);
}
}
}
So, since this is my first shot at dynamics (both in C# and anywhere else really...), I just wanted to ask if I'm doing this right. Obviously the code works as intended, but is this the right way of doing it? Are there any obvious optimizations or pitfalls I'm missing etc?
I've done quite a bit of hacking around with dynamics in C#, I initially thought they would be really neat as I'm a big fan of the dynamic typing done by Ruby/Javascript, but was sadly dissapointed in the implementation. As such, my opinion for "Am I doing it right" comes down to "is this problem a good fit for dynamics" - here are my thoughts on that.
On the whole:
My advice for this specific situation:
Object
. I would suggest that you do this.Tuple
to pass your pairs of data around, and make some custom class(es), but this would probably also improve your code as then you can attach meaningful names to the data instead of just Item1
and Item2
I believe that the dynamic keyword was primarily added to make Microsoft Office interop easier, where previously you had to write quite convoluted code (in C#) to be able to use the Microsoft Office API, Office interface code can be much cleaner now.
The reson for this is that the Office API was originally written to be used by Visual Basic 6 (or VB script); .NET 4.0 adds several language features to make this easier (as well as dynamic, also you get named and optional parameters).
When you use the dynamic keyword, it loses compile-time checking, as the objects using the dynamic keyword are resolved at run-time. There is some memory overhead as the assembly that provides dynamic support has to be loaded in. Also there will be some performance overhead, similar to using Reflection.
I don't think this is a solution for dynamic. Dynamic is useful when you need to work with a bunch of different types conditionally. If this is a string, do something, if it's an int, do something else, if its a Puppy class instance, call bark(). dynamic frees you from having to litter code like this with tons of type casting or ugly generics. Uses for dynamic and other advanced language features are meant for code generators, interpreters etc...
It's a cool feature, but unless your talking to a dynamic language or COM interop, it's only meant for when you have a nasty advanced problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With