views:

98

answers:

4

I have a bunch of IEnumerable Collections which exact number and types is subject of frequent changes (due to automatic code generation).

It looks something like this:

public class MyCollections {
    public System.Collections.Generic.IEnumerable<SomeType> SomeTypeCollection;
    public System.Collections.Generic.IEnumerable<OtherType> OtherTypeCollection;
    ...

At runtime i want to determine each Type and it's count without having to rewrite the code after every code generation. So i am looking for a generic approach using reflection. The result i am looking for is something like:

MyType: 23
OtherType: 42

My problem is that i can't figure how to invoke the Count method properly. Here is what i have so far:

        // Handle to the Count method of System.Linq.Enumerable
        MethodInfo countMethodInfo = typeof(System.Linq.Enumerable).GetMethod("Count", new Type[] { typeof(IEnumerable<>) });

        PropertyInfo[] properties = typeof(MyCollections).GetProperties();
        foreach (PropertyInfo property in properties)
        {
            Type propertyType = property.PropertyType;
            if (propertyType.IsGenericType)
            {
                Type genericType = propertyType.GetGenericTypeDefinition();
                if (genericType == typeof(IEnumerable<>))
                {
                    // access the collection property
                    object collection = property.GetValue(someInstanceOfMyCollections, null);

                    // access the type of the generic collection
                    Type genericArgument = propertyType.GetGenericArguments()[0];

                    // make a generic method call for System.Linq.Enumerable.Count<> for the type of this collection
                    MethodInfo localCountMethodInfo = countMethodInfo.MakeGenericMethod(genericArgument);

                    // invoke Count method (this fails)
                    object count = localCountMethodInfo.Invoke(collection, null);

                    System.Diagnostics.Debug.WriteLine("{0}: {1}", genericArgument.Name, count);
                }
            }
        }
+2  A: 

That is going to involve some MakeGenericMethod - and a lot of reflection generally. Personally, I would be tempted to just simplify by ditching the generics in this case:

public static int Count(IEnumerable data) {
    ICollection list = data as ICollection;
    if(list != null) return list.Count;
    int count = 0;
    IEnumerator iter = data.GetEnumerator();
    using(iter as IDisposable) {
        while(iter.MoveNext()) count++;
    }
    return count;
}

You can cast to the non-generic IEnumerable trivially, even if fetching via reflection.

Marc Gravell
Of course, this is a nice idea. But i am looking for the solution to my flaw in MakeGenericMethod.
embee
Can't we just `foreach` over `data`, or do we need to deal with the enumerator explicitly?
AakashM
@embee - 2 seconds, I'll look...
Marc Gravell
Why are some many people 'afraid' to lose generics by casting to `IList` or `IEnumerable`?
leppie
@AakashM, Marc, approach avoids needless calls to `Current` that might (depending on enumerator) be expensive and might not be optimised away.@Marc, I'd do the first test as a test for ICollection, rather than IList, as ICollection is where the Count property is defined for IList, and this way you'll catch other collection types with a built-in (and likely more efficient) Count.
Jon Hanna
@Jon Hanna: If `Current` is expensive, you are doing it wrong :)
leppie
@Jon - fair point re ICollection; tweaked.
Marc Gravell
@AakashM - probably, but simply: we don't need that data. `foreach` would be simpler, of course.
Marc Gravell
@leppie. Yes, but who's to say the person (may not be you) didn't do it wrong! If they are converting from another source then you might do that on Current rather than on MoveNext. Unlikely to be unbelievably expensive, but possibly non-trivial, and not the worse approach in the world (not how I'd do it, but meh).
Jon Hanna
@Jon Hanna: `MoveNext` generally sets up the current value as it has to return a boolean to specify whether it was found or not.
leppie
@leppie. Yes, it generally does, though it could go no further than (say) calling Read() on a DataReader.
Jon Hanna
+1  A: 
var count = System.Linq.Enumerable.Count(theCollection);

Edit: you say it's generated though, so can you not just generate a properties with calls to Count()?

public class MyCollections
{
    public System.Collections.Generic.IEnumerable<SomeType> SomeTypeCollection;
    public System.Collections.Generic.IEnumerable<OtherType> OtherTypeCollection;

    public int CountSomeTypeCollection
    {
        get { return this.SomeTypeCollection.Count(); }
    }

    ...

HTH,
Kent

Kent Boogaart
Sorry, missed the fact that you don't know `T` at compile-time.
Kent Boogaart
MyCollections is generated automaticly and overwritten quite often. This is why i have to use reflection from the outside. I know it's tricky but it should be possible...
embee
+2  A: 

If you insist on doing it the hard way ;p

Changes:

  • how you obtain countMethodInfo for a generic method
  • the arguments to Invoke

Code (note obj is my instance of MyCollections):

    MethodInfo countMethodInfo = typeof (System.Linq.Enumerable).GetMethods().Single(
        method => method.Name == "Count" && method.IsStatic && method.GetParameters().Length == 1);

    PropertyInfo[] properties = typeof(MyCollections).GetProperties();
    foreach (PropertyInfo property in properties)
    {
        Type propertyType = property.PropertyType;
        if (propertyType.IsGenericType)
        {
            Type genericType = propertyType.GetGenericTypeDefinition();
            if (genericType == typeof(IEnumerable<>))
            {
                // access the collection property
                object collection = property.GetValue(obj, null);

                // access the type of the generic collection
                Type genericArgument = propertyType.GetGenericArguments()[0];

                // make a generic method call for System.Linq.Enumerable.Count<> for the type of this collection
                MethodInfo localCountMethodInfo = countMethodInfo.MakeGenericMethod(genericArgument);

                // invoke Count method (this fails)
                object count = localCountMethodInfo.Invoke(null, new object[] {collection});

                System.Diagnostics.Debug.WriteLine("{0}: {1}", genericArgument.Name, count);
            }
        }
    }
Marc Gravell
Thank you very much! After examining your response i suddently understood my mistake. In fact only the arguments to Invoke had to be adjusted (of course!) and then all of my original implementation worked as expected.Even tough your Linq approach to retrieving countMethodInfo looks sexy it feels a bit to complcated for me ;-)
embee
@embee - when I tried your original code, I got `null` for the methodinfo...
Marc Gravell
@marc it works well for silverlight/windows phone
embee
+1  A: 
Abel
The reason for reflection is simple: what you label as "yourtype" is not known at compile time (or at least it changes frequently).
embee
@embee: that, I understood. But if it changes frequently (it is generated) it is ideal for generics. But in all honesty, I do not know enough of your situation to know for certain whether your approach is a good one considering.
Abel
Probably i just dont understand generics enough to follow your suggestion, sorry. In this scenario MyCollections is something that you must take for granted.
embee
@embee: I added a note, hope it clarifies it a bit. It is my understanding that you are currently doing more than is necessary to invoke a static method with a collection of a type of which you do not know the type at compile time. But maybe I've just utterly misunderstood? :)
Abel
On new reading, I see that I follow quite closely what's already been said. I was distracted by your foreach-loop. Sorry. So, the above should read: this is all it takes for calling generic method `Count`. Your loop is part of your base logics.
Abel