3

I have a method that accepts a basic generic variable in the method signature:

public void Foo<T>(T val) {

    if (val is IEnumerable) {
        Bar(val)
    }
}

...Logic if not enumerable

The signature for Bar looks like this:

private void Bar<T>(IEnumerable<T> vals) {
    ...Logic if enumerable
}

The logic is very lengthy and different based on whether the item passed in is enumerable or not. I can't get Bar(val) to work. I tried the following, let me know what I'm missing:

Bar(val)

Bar((IEnumerable<T>)val) (Compiles, but results in this error at run-time when tried with a List of ints:

Unable to cast object of type 'System.Collections.Generic.List`1[System.Int32]' to type 'System.Collections.Generic.IEnumerable`1[System.Collections.Generic.List`1[System.Int32]]'.'

Bar((IEnumerable)val)

Brettyoke49
  • 111
  • 1
  • 9
  • You are checking that something is `IEnumerable` and then passing it as `IEnumerable`. – GSerg Dec 27 '22 at 23:33
  • `if (val is IEnumerable e) { Bar(e); }`, but like I said, that implies `Bar(IEnumerable vals)`, not `Bar(IEnumerable vals)`. – GSerg Dec 27 '22 at 23:46
  • 2
    Simplest way is probably `Bar((dynamic) val)`, although some people will lynch me for even mentioning `dynamic`. The alternative is getting tricksy with `MakeGenericMethod`, which is going to be very involved since you also need to get the generic type `U` of the `IEnumerable` that is `val` (which is not the same as `T`, as you've discovered). Something like `Expression e = () => Bar(default); ((MethodCallExpression) e.Body).Method.GetGenericMethodDefinition().MakeGenericMethod(val.GetType()).Invoke(this, new object[] { val });`, isn't that fun? – Jeroen Mostert Dec 27 '22 at 23:47
  • I am able to retrieve the `T` in the `IEnumerable` if it helps. I should have put this in the main post, but I've tried `Bar((IEnumerable<[valInnerType]>)val)` where `[valInnerType]` is the type being enumerated. Doesn't compile. – Brettyoke49 Dec 27 '22 at 23:50
  • If you have the type at the call site it's easier; `Bar((IEnumerable) (object) val)` will compile and do the conversion at runtime. – Jeroen Mostert Dec 27 '22 at 23:51
  • I hope the accepted answer will explain why the intuitive `if (val is IEnumerable e) Bar(e)` does not work and why the cast throws an exception. – Wyck Dec 28 '22 at 00:14
  • @Wyck Why that would not work? I just tried and it worked fine without any problems (indeed class need to implement iterator, but that's quite obvious - `class MyClass : IEnumerable {...}`) – Alexei Levenkov Dec 28 '22 at 01:17

2 Answers2

2

The only option without changing the signature and/or using dynamic is to dive into reflection. You need to get the closed generic type of IEnumerable<TInner> (where T : IEnumerable<TInner>), construct corresponding closed version of Bar and invoke it. Something along this lines:

void Foo<T>(T val)
{
    var genericEnumInterface = typeof(T).GetInterfaces()
        .FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
    if (genericEnumInterface is not null)
    {
        MethodInfo mi = ...; // get Bar via reflection
        var constructedMethod = mi.MakeGenericMethod(genericEnumInterface);
        constructedMethod.Invoke(this, new[]{(object)val}); // assuming Bar is in the same class as Foo
    }
}

Note that there are types which will implement IEnumerable<T> which you possibly will not want to process via Bar like string which is IEnumerable<char>. Another concern is that reflection can cause noticable performance hit in some cases so you can try caching it (see for example this or this answer).

Also personally I would try to avoid such generic method as Foo due to possible misuse and corner cases like string.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
0

If you don't want to go down the reflection route there is another option:

if (val is IEnumerable) 
{
    Bar(val as dynamic);
}

You may not want to go down that rabbit hole though.

SBFrancies
  • 3,987
  • 2
  • 14
  • 37