1

I know this is most probably a simple question involving Generics, or, quite possibly, just a big "no-no" overall, but I'm curious to see if I can get this working.

I'm trying to create a method that takes in an object and, if the object is an enumerable type, to pass it off to a method that can work on any kind of enumerable, but I'm getting terribly stuck.

My current code for the method that takes in an object looks something like the following:

private void MyMethod()
{
    Dictionary<string, object> MyVals = new Dictionary<string,object>();

    ... fill the MyVals dictionary ...

    object x = MyVals["Val_1"];
    Type type = x.GetType();

    // Check if we have a series of values rather than a single value
    if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type))
        Test(x);
}

Then, I thought I could write something like one of the following as the method signature for my Test method:

1. Write it as if it's an IEnumerable of object:

private void Test(IEnumerable<object> MyEnumeration)

Tried calling it via:

Test((IEnumerable<object>)x);

Leads to run-time error that it cannot cast from IEnumerable<int> to IEnumerable<object>

2. Try using Generics:

private void Test<T>(IEnumerable<T> MyEnumeration)

Tried calling it via:

Test(x);

Leads to design-time error that the signature is incorrect / invalid arguments.

or via:

Test<type>(x);

Leads to a design-time error that the type or namespace type could not be found.


How could this be done OR is it just bad programming practice and there is a better way to do this?

Thanks!

John Bustos
  • 19,036
  • 17
  • 89
  • 151
  • 1
    No reason why either fails from looking at the signature. However, "neither works" is a *terrible* explanation of what is going on, and that makes this really hard to address. What specifically doesn't work when you try this, and what in general is the Test method attempting to accomplish? – Travis J Jan 21 '16 at 21:06
  • 1
    Fair enough, @TravisJ. Let me update my question... – John Bustos Jan 21 '16 at 21:07
  • 1
    Question updated with my attempts - Hope that makes (at least a bit) more sense. – John Bustos Jan 21 '16 at 21:13

3 Answers3

2

The problem is with this code:

if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type))
    Test(x);

You now know that x is an IEnumerable, but it still treated as an object when the compiler determines which method signatures are compatible.

If you did this:

if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type))
{
    IEnumerable asEnumerable = (IEnumerable)x;
    Test(asEnumerable);
}

Then it can be passed to

void Test(IEnumerable t)
{
}

Or, if you really want to use IEnumerable<object>:

if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type))
{
    IEnumerable<object> asEnumerable = ((IEnumerable)x).Cast<object>();
    Test(asEnumerable);
}

And, if you want an IEnumerable<T>, see Jon Skeet's answer to a different question.

Community
  • 1
  • 1
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • @TravisJ Correct. I'm just solving option one. You need Jon Skeet for option two: http://stackoverflow.com/questions/812673/convert-cast-ienumerable-to-ienumerablet – Andrew Shepherd Jan 21 '16 at 21:17
  • Yes, actually you at least included the option for the IEnumerable usage so I removed my comment. And I agree that using Jon Skeet's suggested type inference approach is the best way to call when there are issues with making a type inference call. – Travis J Jan 21 '16 at 21:24
0

This has a bad code smell so I would elaborate what it is you actually want to do and maybe someone can help. Your runtime error is due to the fact the collection you have is in fact NOT an IEnumerable[object] but is IEnumerable[int]. You would have to call .Cast<object> first.

So it would be a double cast.

Test(((IEnumerable<int>)x).Cast<object>);

Again, this has a terrible code smell. You should elaborate how the data is coming in and I am sure someone can help you.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
N_tro_P
  • 673
  • 5
  • 15
0

You should create two overloads of Test() method. one which would handle IEnumerable and another which handles IEnumerable<T>. Why do you need method overload? Because IEnumerables are not generic IEnumerable<T> and at run time you won't be knowing the type of generic type to use Cast on object.

Below is the sample showing how exactly you can achieve what you're looking for:

 public void MyMethod()
 {
     // Sample object with underlying type as IEnumerable
     object obj1 = new ArrayList();

     // Sample object with underlying type as IEnumerable & IEnumerable<T>
     object obj2 = (IList<string>)new List<string>();

        if (typeof(IEnumerable).IsAssignableFrom(obj1.GetType()))
        {
            if (!obj1.GetType().IsGenericType)
            {
                // Handles case of IEnumerable
                Test((IEnumerable)obj1);
            }
            else
            {
                // Handles case of IEnumerable<T>
                InvokeGenericTest(obj2);
            }
        }
 }

 public void Test(IEnumerable source)
 {
     Console.WriteLine("Yes it was IEnumerable.");
 }


 public void Test<T>(IEnumerable<T> source)
 {
     Console.WriteLine("Yes it was IEnumerable<{0}>.", typeof(T));

     // Use linq with out worries.
     source.ToList().ForEach(x => Console.WriteLine(x));
 }

 /// <summary>
 /// Invokes the generic overload of Test method.
 /// </summary>
 /// <param name="source"></param>
 private void InvokeGenericTest(object source)
 {
     Type t = source.GetType();

     var method = this.GetType().GetMethods().Where(x => x.IsGenericMethod && x.Name == "Test").First();

     var genericMethod = method.MakeGenericMethod(t.GenericTypeArguments.First());
     genericMethod.Invoke(this, new object[] { source });
 }
vendettamit
  • 14,315
  • 2
  • 32
  • 54