24

I have added a code in my startup class (.net core 3.1) to return the type based on parameter and I get compile-time errors.

I have created a running example in sharplab. if switch expression contains the string or other objects it runs fine.

working example 1:

var x = key switch
            {
                "myhandler1" => "something",
                "myhandler2" => "something else",
                _ => "default case"
            };

working example 2:

object obj =  s switch {
            "a" => new object(),
            "b" => new DateTime(),
            _ => throw new NotImplementedException()
        };

Error example:

interface IHandler { }
public class BaseHandler { }
public class MyHandler1: BaseHandler, IHandler { }
public class MyHandler2: BaseHandler, IHandler { }

class Program
{
    static void Main(string[] args)
    {

        var key = "myhandler1";

        var handler = key switch
        {
            "myhandler1" => new MyHandler1(),
            "myhandler2" => new MyHandler2(),
            _ => throw new NotImplementedException()
        };

        var x = key switch
        {
            "myhandler1" => "something",
            "myhandler2" => "something else",
            _ => "default case"
        };

        Console.WriteLine("Hello World!");
    }
}

original problem (needs fixing):

serviceCollection.AddTransient<Func<string, IHandler>>(sp => key =>
            {
                return key switch
                {
                    Constants.Brand => sp.GetService<Handler1>(),
                    Constants.Series => sp.GetService<Handler2>(),
                    _ => throw new NotImplementedException()

                };
}

found this link: https://github.com/dotnet/csharplang/issues/2728

Thanks to Pavel and Marc, below is the fix:

serviceCollection.AddTransient<Func<string, IHandler>>(sp => key =>
            {
                return key switch
                {
                    Constants.Brand => (sp.GetService<Handler1>() as IHandler),
                    Constants.Series => (sp.GetService<Handler2>() as IHandler),
                    _ => throw new NotImplementedException()

                };
}
Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
Kamran Pervaiz
  • 1,861
  • 4
  • 22
  • 42
  • 3
    It would work if `Handler1` and `Handler2` had a common base type/interface. – phuzi Apr 02 '20 at 14:01
  • 1
    Since `DateTime` can be downcast to `object`, that works, but I assume that `MyHandler2` doesn't inherit from `MyHandler1` or vice versa. If these two types have a common base type, you can use that instead of `var` to give the compiler enough knowledge. ie. `MyHandlerBase handler = key switch { ... };`. – Lasse V. Karlsen Apr 02 '20 at 14:01
  • 2
    do `MyHandler1` and `MyHandler2` share a common base type? perhaps use that instead of `var` – Marc Gravell Apr 02 '20 at 14:01
  • @MarcGravell yes they both have baseType and interface. in sharplab I used BaseHandler/Ihandler it fixes the problem but when I apply same changes in startup DI class it still gives error. – Kamran Pervaiz Apr 02 '20 at 14:09
  • maybe `return (IHandler) key switch...` ? – Marc Gravell Apr 02 '20 at 14:22
  • @MarcGravell your example will try to convert string key to IHandler and will fail, I just tested and it gave compile errors. I have updated the question with answer. Thank you – Kamran Pervaiz Apr 02 '20 at 14:35

3 Answers3

39

You should explicitly declare a type of handler, instead of var

IHandler handler = key switch //or BaseHandler handler = key switch
{
    "myhandler1" => new MyHandler1(),
    "myhandler2" => new MyHandler2(),
    _ => throw new NotImplementedException()
};

In your sharplab sample both handlers implement IHandler interface and inherit BaseHandler class, compiler simply doesn't know which type to use, you should tell it him explicitly

interface IHandler { }
public class BaseHandler { }
public class MyHandler1 : BaseHandler, IHandler { }
public class MyHandler2 : BaseHandler, IHandler { }

The same is true for the dependency injection sample, you should explicitly declare a type (assuming that Handler1 and Handler2 implement IHandler)

return key switch
{
    Constants.Brand => sp.GetService<Handler1>(),
    Constants.Series => (IHandler) sp.GetService<Handler2>(),
    _ => throw new NotImplementedException()
};

You can do it only for one constant, compiler is smart enough to do the rest of job for you

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
2

You're dealing with a covariance issue. You've specified that the return type of the Func should be IHandler, but this type param is invariant. As such, you have to actually return IHandler, not just a class that implements it.

serviceCollection.AddTransient<Func<string, IHandler>>(sp => key =>
{
    return key switch
    {
        Constants.Brand => (IHandler)sp.GetService<Handler1>(),
        Constants.Series => (IHandler)sp.GetService<Handler2>(),
        _ => throw new NotImplementedException()
    };
}
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
1

var is fussy - it wants things unambiguous, and it isn't obvious what type handler should be here since MyHandler1 and MyHandler2 are different types; basically, pick some common base type or implemented interface from MyHandler1 and MyHandler2, and use that instead of var. At worst case, object should suffice:

object handler = key switch
{
    "myhandler1" => new MyHandler1(),
    "myhandler2" => new MyHandler2(),
    _ => throw new NotImplementedException()
};

The compiler won't attempt to do this itself.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • thanks, I have updated my question for DI. not sure why it is not working for Dependency Injection when sharplab is working for common base type – Kamran Pervaiz Apr 02 '20 at 14:12