Reputation: 20324
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?
Upvotes: 10
Views: 2267
Reputation: 123612
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
Upvotes: 4
Reputation: 17356
I haven't looked at it closely yet but is the use of dynamic keyword really necessary for your use when declaring the collection? In .NET 4.0 there are also new mechanisms to support covariance and contravariance which means you should also be able to use the code below.
var listCollection = new List<IEnumerable<object>>();
listCollection.Add(new List<int>());
The drawback here is that your list holds read only IEnumerable instances instead of something that could be modified directly if that was something that was required in your implementation.
Other than that consideration I think the use of dynamics are fine as you are using them but you do sacrifice a lot of the safety mechanism that C# normally provides. So what I would recommend is that if you use this technique I would recommend writing it within a well contained and tested class that doesn't expose the dynamic type to any larger body of client code.
Upvotes: 0
Reputation: 86064
Instead of using dynamics here, I think you can accomplish the same thing by using by using IList
. (non-generic) Both eliminate compile time type checking, but since generic lists also implement IList
, you can still get run-time type checking using IList
.
Also, side question, why did you use .Aggregate()
instead of .Max()
to find the maximum length?
Upvotes: 1
Reputation: 4119
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.
Upvotes: 1
Reputation: 2821
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.
Upvotes: 3