0

The following code would compile if foo is casted to either a type implementing IEnumerable or dynamic. Is there a way to achieve the same without casting foo ? I am aware I could do two methods and don't wish to force more constraint to T.

interface IDummy
{}

class FooBar<T> where T : class
{
    void Bar(T foo)
    {
       if (foo is IEnumerable<IDummy>)
          foreach (var item in foo)
             B(item);
       else if(foo is IDummy)
          B(foo);                      
    }  

    void B(IDummy item)
    {

    }
}
Baguette
  • 27
  • 6
  • 4
    Using generics doesn't solve anything here. Just have `Bar` accept an `IEnumerable`. –  Feb 13 '19 at 18:56
  • It sounds like you want to iterate over an object that may or may not be an `IEnumerable`. The only way to do that is to cast the object as an `IEnumerable` and if the cast succeeds, iterate. You could do that a bit easier using `if (foo is IEnumerable fooE) foreach (var item in fooE)` if you're using C# 7 or above. – Heretic Monkey Feb 13 '19 at 19:01
  • @Amy I edited to provide a more intent with a use of genericity. I focused too much on doing a minimal dummy code. – Baguette Feb 13 '19 at 19:07
  • No, it's not possible to do that without casting `foo` or enforcing a constraint `where T : IEnumerable`. You'll get a compile error if you try to pass a type to the `foreach` statement that isn't guaranteed to be an `IEnumerable` (or doesn't have a the public parameterless `GetEnumerator()` method whose return type is a class, struct, or interface). – Rufus L Feb 13 '19 at 19:12
  • 2
    Anytime you find yourself checking the type of `T` in a generic method, it's likely not a great candidate for generics. – Rufus L Feb 13 '19 at 19:14
  • 3
    Generics that perform type tests internally are *usually* a bad idea. So, you've promised me that `FooBar` can work with any reference type. I then write a perfectly legal call to `A` having parameterized with a type that is neither `IEnumerable` nor `Dummy`. The method, far from working, does nothing. Even if it throws a runtime exception, you've "lied" to me and the compiler about what types you can *actually* work with. – Damien_The_Unbeliever Feb 13 '19 at 19:15
  • 1
    If someone should be able to either pass in an `IDummy` or an `IEnumerable` then *have two overloads, with each of those two types as parameters*, so that callers know those are the two types your method accepts. – Servy Feb 13 '19 at 19:20
  • @Damien_The_Unbeliever Great answer :) – Baguette Feb 13 '19 at 19:21
  • It's also a minefield. Suppose I define `class Abc: IDummy, IEnumerable {...}`. In my mind, it's an `IDummy` first and foremost and contains an `IEnumerable` secondarily. I can't use your generic as declared if I want the `IDummy` behaviour. At least with overloads, I can cast away it's concreteness and get the call I want/need. – Damien_The_Unbeliever Feb 13 '19 at 19:37
  • (And whilst I can do the same by parameterizing by `IDummy` or `IEnumerable` with the generic version, why was it generic anyway if I have to only use interfaces?) – Damien_The_Unbeliever Feb 13 '19 at 19:44

1 Answers1

0

I'm not quite sure what your reasons are for not wanting to cast, but would something like this work for you?

interface IDummy
{}

class FooBar<T> where T : class
{
    void Bar(T foo)
    {
       if (foo is IEnumerable<IDummy> enumerableFoo)
          foreach (var item in enumerableFoo)
             B(item);
       else if(foo is IDummy)
          B(foo);                      
    }  

    void B(T item)
    {

    }
}
Nanna
  • 515
  • 1
  • 9
  • 25
  • Besides the compilation error on `B(item)` (which is my fault, B signature is wrong), isn't this just syntaxic sugar for `IEnumerable enumerableFoo = foo as IEnumerable;` so still a cast ? – Baguette Feb 13 '19 at 19:26
  • Yes basically. Just thought I'd suggest it since using "is" was not a stopper for you. Out of curiosity, why do you not want to cast foo? – Nanna Feb 13 '19 at 19:29
  • Is was not a stopper because I believed is operator is syntaxic sugar for `foo.GetType() == typeof(T)`, so no cast. Am I wrong ? The whole question is purely out of curiosity. Any sane code would use method overload (which doesn't use cast indeed) – Baguette Feb 13 '19 at 19:32
  • No you're right, no cast there even though they are not completely the same (https://stackoverflow.com/questions/983030/type-checking-typeof-gettype-or-is). Indeed an interesting question, I don't have the answer then I'm afraid. – Nanna Feb 13 '19 at 19:42
  • 1
    See also this performance comparison of the is operator vs type comparison. https://stackoverflow.com/a/11518994/1056639 – Grax32 Feb 13 '19 at 20:59
  • @Baguette CIL has a command `isinst` that the is operator maps to, so it is better/different than the type comparison equality check. – Grax32 Feb 14 '19 at 12:45