3

I am trying to write a method that accepts as a parameter any IEnumerable<T>; and call it from code where T will not be known until runtime.

I could write it as a generic method and then invoke it with reflection when I want to use it, but for a variety of reasons I decided to use type dynamic. However when I passed in a strongly typed collection output from an Entity Framework function import (a System.Data.Objects.ObjectQuery<T>) and tried to access the Count() method, it threw an exception to the effect that System.Data.Objects.ObjectQuery<T> didn't have a Count() method, which is not the case.

I've worked around this by limiting my interaction with the object to a foreach statement, thus making the compiler figure out how to enumerate it, so it's not a problem. I'm more interested in understanding why I can't do this. My guess is that it's either that Count() is a method on a base class or interface and therefore not accessible for some reason, or that the compiler usually translates calls to Count() to Count<T> during compilation and can't do so in this case? Help appreciated.

Edit: I feel like it's probably the case that the compiler is not translating the non-generic Count() into a generic Count<T>. When you call Count() on an IEnumerable<T>.Count() you are actually calling Count<T>(), it's just syntactic sugar that the compiler takes care of. I think it's probably not making the same translation of Count() to Count<T>() at runtime. It would be nice to get confirmation of this because this is a pretty annoying limitation of use of generics with type dynamic if so.

Also edited to add code:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled by user code HResult=-2146233088 Message='System.Data.Objects.ObjectResult' does not contain a definition for 'Count' Source=Anonymously Hosted DynamicMethods Assembly StackTrace: at CallSite.Target(Closure , CallSite , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0) at MyNamespace.HtmlHelpers.RenderTable(Object list, String id, String cssClass) in [snip]

And an example of the code that throws the exception:

public static MvcHtmlString RenderTable(dynamic list, string id, string cssClass)
{
    int x = list.Count();
    ........
    ........
}
ChrisV
  • 1,309
  • 9
  • 15
  • whats the actual error message. you aren't doing us a favor by trying to paraphrase – Robert Levy Jun 17 '14 at 04:29
  • Wrt "will not be known until runtime": At the usage-site, T *must* be resolved to a type at compile time (or itself be a generic type parameter). – user2864740 Jun 17 '14 at 04:31
  • @user2864740 That isn't really the case. Generic methods can be invoked on arbitrary types at runtime using reflection. See http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic-method for instance. – ChrisV Jun 17 '14 at 05:21

2 Answers2

0

The reason list.Count() gives a runtime error when list is a dynamic is that Count is an extension method - Enumerable.Count<T> - and extension methods can only be discovered at compile time. (Somebody correct me if I'm wrong on this - this is my understanding.)

Before getting the direct answer to your question, take a look at this little toy program.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<int> a = Enumerable.Range(1, 3);
            IEnumerable b = a;
            IEnumerable<double> c = b.SelectTimesPi();
            foreach (var x in c)
            {
                Console.WriteLine(x);
            }
            Console.ReadLine();
        }
    }

    static class Extensions
    {
        public static IEnumerable<double> SelectTimesPi(this IEnumerable source)
        {
            return source
                .Cast<dynamic>()
                .Select(x => Math.PI * x)
                .Cast<double>();
        }
    }
}

Its output is the following.

3.14159265358979
6.28318530717959
9.42477796076938

The SelectTimesPi extension method takes any IEnumerable<T> where T is only known at runtime, and then manipulates its elements as dynamic values. The source could be IEnumerable<byte>, IEnumerable<short>, IEnumerable<float>, etc.

You could of course do the same thing with a non-extension method, like you asked. I just used that as the example since it looks like your aim is to ultimately create an extension method.

The trick is in the use of Enumerable.Cast<dynamic> on an IEnumerable.

Back to your original question. All you have to do is make your RenderTable method take the list as an IEnumerable instead of a dynamic, then use Enumerable.Cast<dynamic>.

public static MvcHtmlString RenderTable(IEnumerable list, string id, string cssClass)
{
    IEnumerable<dynamic> dynamicList = list.Cast<dynamic>();
    int x = dynamicList.Count();
    ...
}

Now the call to Count is not dynamically dispatched, so the extension method will work fine.

In summary:

IEnumerable<int> x = ...;
(x as IEnumerable).Cast<dynamic>().Count(); // does work
(x as dynamic).Count(); // does not work
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • Cute. I realised that the answer for me is even simpler; I'd forgotten that `IEnumerable` inherits from `IEnumerable`, so I can just cast my result sets down to `IEnumerable` and throw them around like that. I'm using reflection on the items anyway so there's no need for type dynamic. Your post is a nice demo, but I won't mark it as an answer as I'm still curious what the explanation for the exception I saw is. – ChrisV Jun 17 '14 at 05:48
  • @user3746897 This is the explanation for the exception. You're trying to dynamically call an extension method. That won't work, because the instance your `list` variable is pointing to *does not have* a `Count` method. Also, the LINQ methods are only defined on generic `IEnumerable`, so you actually can't just call `Count` on an `IEnumerable`. `Enumerable.Cast` and `Enumerable.OfType` are the only two extension methods on `IEnumerable`. – Timothy Shields Jun 17 '14 at 05:50
  • 1
    http://connect.microsoft.com/VisualStudio/feedback/details/771364/use-of-extension-methods-using-a-dynamic-variable-doesnt-work – Andrew Savinykh Jun 17 '14 at 05:54
0

The issue can be demonstrated with this small program:

using System;

namespace SO24255725
{
    public static class Extension
    {
        public static int Count(this Program a)
        {
            return 42;
        }
    }


    public class Program
    {
        static void Main()
        {
            Program test = new Program();
            PrintCount(test);
        }

        static void PrintCount(dynamic data)
        {
            Console.WriteLine(data.Count());
        }
    }
}

Here is why:

The dynamic feature cannot bind arbitrarily at runtime to extension methods because the extension method feature is defined to be scoped only to the 'using's of the compilation unit. At runtime, the 'using' information is gone, so the runtime binder cannot resolve the extension method. This is by design...

Source: http://connect.microsoft.com/VisualStudio/feedback/details/771364/use-of-extension-methods-using-a-dynamic-variable-doesnt-work

You did not list the "variety of reasons" of why you decided to use dynamic. May be you should not.

Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158