10

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&lt;ICollection&lt;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&lt;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?

Julian
  • 20,008
  • 17
  • 77
  • 108
  • Wouldn't it be much easier to work with a DataTable? – Oliver May 10 '11 at 10:10
  • 2
    @Oliver: as I briefly mentioned in my question: yes, there might very well be better ways to solve this specific problem without using dynamics. But since this was my first chance where I found the dynamic type to be even remotely useful, I'm more interested in this aspect of the question. – Julian May 10 '11 at 10:43
  • Try http://codereview.stackexchange.com/ – liori May 10 '11 at 11:25
  • 1
    @liori: thanks, wasn't aware of that SE site. I do however feel that the question fits just as well here on SO, so I'll leave it here for now at least... – Julian May 10 '11 at 12:18

5 Answers5

4

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.

  • the performance hit on load to pull in and JIT all the dynamic-related assemblies can be quite severe.
  • The C-Sharp runtime binder internally throws and catches an exception the first time a method is resolved dynamically. This happens per call-site (i.e. if you have 10 lines of code calling methods on a dynamic object you get 10 exceptions). This is really annoying if you have your debugger set to "break on first chance exceptions", and it also fills up the debug output window with first chance exception messages. You can suppress these, but visual studio makes it annoying to do so.
  • These two things add up - on a cold start your app can take noticeably longer to load. On a core i7 with an SSD, I found that when my WPF app first loaded all the dynamic stuff, it would stall for about 1-2 seconds loading assemblies, JITing, and throw-catching exceptions. (Interestingly IronRuby doesn't have these problems, it's implementation of the DLR is much better than C#'s)
  • Once things are loaded the performance is very good.
  • Dynamics kill intellisense and other nice features of visual studio. While I personally didn't mind this as I have a background of doing lots of ruby code, several other developers in my organization got annoyed.
  • Dynamics can make debugging a LOT harder. Languages like ruby/javascript provide a REPL (interactive prompt) which helps them, but C# doesn't yet have one. If you're just using dynamic to resolve methods, it won't be so bad, but if you try and use it to dynamically implement data structures (ExpandoObject, etc) then debugging becomes a real pain in C#. My co-workers were even more annoyed at me when they had to debug some code using an ExpandoObject.

On the whole:

  • If you can do something without needing dynamics, don't use them. The way C# implements them is just too awkward and your co-workers will be angry with you.
  • If you only need a very small dynamic feature, use reflection. The oft-quoted "performance issues" from using reflection are often not a big deal.
  • Especially try and avoid dynamics in a client app due to the load/start performance penalties.

My advice for this specific situation:

  • It looks like you can probably avoid dynamics here by just passing things around as Object. I would suggest that you do this.
  • You would have to switch away from using 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
Orion Edwards
  • 121,657
  • 64
  • 239
  • 328
  • +1 for a really thorough and good answer. Also appreciate the comments regarding my specific solution. In my case, the code is only used a few places internally in a project, so I won't bother making any significant changes to it. But I'll for sure take your advice with me for future references :) – Julian Nov 10 '11 at 06:51
3

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.

RickL
  • 2,811
  • 3
  • 22
  • 35
  • Thanks RickL :) I'm aware of the concept that you loose compile-time checking (resolving types at run-time is kind of the main concept of dynamics, right?) and that there will be some overhead regarding performance. I'm more after if the code I've written is particularly bad in any way (without focusing too much focus on that this might not be the perfect example), if there are any "hidden traps" I've stepped into and so on. – Julian May 10 '11 at 11:10
  • 3
    I have a feeling that generally, it's bad programming practice to use the dynamic keyword unless it's for interfacing with COM or scripting languages (e.g. IronPython). IMHO, it would be better to use the standard object-oriented programming concepts where possible. – RickL May 10 '11 at 11:18
  • I'll agree with you, at least to some extent. Dynamics *can* be a bad programming practice, at least if you overuse them (but I guess it's that way with most things). Now I've only briefly started looking at them myself, but my impression is that there are a few valid cases where dynamics will make your life much easier (without totally messing up your code). And of course, if the code in my question is a particularly bad example of how to use dynamics, I'll be very glad to hear about that as well :) – Julian May 10 '11 at 13:13
1

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.

Tjaart
  • 3,912
  • 2
  • 37
  • 61
1

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?

recursive
  • 83,943
  • 34
  • 151
  • 241
  • I agree, at least with IList you will be able to gain the ability to call things like Count without having to know about or cast to the underlying type. Also the use of the dynamic keyword can be reserved for when you actually need to deal with each element instead of maintaining the list itself as dynamic throughout the code. – jpierson May 18 '11 at 05:02
  • @recursive: it might be that I'm missing something obvious, but if I were to use a non-generic IList, wouldn't I at some point have to do some casting? And wouldn't that then be just as good (or bad...) as using dynamic? – Julian May 18 '11 at 15:25
  • If you cast to a known type, as opposed to using a dynamic, from then on you get compile time type safety and intellisense. To me, that seems to be an advantage over using dynamic as a generic parameter. – recursive May 18 '11 at 15:30
  • @recursive: regarding ´.Aggregate()´ VS ´.Max()´: that's just a combination of me not being too solid with the LINQ extension methods, and too blindly trusting ReSharper's suggestions for conversions from for-loops to LINQ-expressions. Thanks for enlightening me :) – Julian May 18 '11 at 15:30
  • @recursive: I'm still not sure I follow you completely. If you look at my code, the "real logic" is in the generic function ´FillList(ICollection list, T fillValue, int maxNumberOfItems)´. I don't see how, if I just had a non-generic IList, I would be able to cast the objects in that list to both a ´Tuple, String>´, a ´Tuple, int>´ or a ´Tuple, Boolean>´ etc. without adding a lot of type checking... (leaving work now btw, so probably won't be able to follow up comments before tomorrow) – Julian May 18 '11 at 15:43
  • I don't know if this is appropriate for your use case, but you could eliminate the generic parameter from that method, and use `object` instead of `T`. I may be wrong, but I don't think the generic is buying you any type safety when you're using dynamic. – recursive May 18 '11 at 16:06
0

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.

jpierson
  • 16,435
  • 14
  • 105
  • 149
  • .net 4 does indeed support covariance, but `List` is not covariant since it has inputs and outputs in the generic type. – recursive May 18 '11 at 04:10
  • @recursive - Ahh good point. Just so that others know what you are referring to, I found this good reference from SO to share and I'll update my answer accordingly. http://stackoverflow.com/questions/5832094/covariance-and-ilist – jpierson May 18 '11 at 04:57
  • the new mechanisms for contra- and covariance was new to me, but as you point out, I'll end up with a list of read only IEnumberable instances (and since the whole point of my code is to add items to some of those instances, that won't do me much good). Thanks for the rest of your input though :9 – Julian May 18 '11 at 15:23