简体   繁体   中英

Return object(T) from switch statement

In my code I am having ManagerService of T type which can be BaseManager or AnotherManager :

abstract class BaseManager { }
abstract class FooManager : BaseManager { }
abstract class AnotherManager : BaseManager { }
class ManagerService<T> where T : BaseManager { }

Now I want to get specific object by string :

static ManagerService<T> GetService<T>(string serviceName)  where T : BaseManager
{
    switch(serviceName)
    {
        case "foo": return new ManagerService<FooManager>();
        case "another": return new ManagerService<AnotherManager>();
    }
    throw new ArgumentException("Service not found");
}

And here would be usage:

static void Main(string[] args)
{
    var serviceBase = GetService("foo"); // it should return ManagerService<FooManager>
    var serviceAnother = GetService("another"); // it should return ManagerService<AnotherManager>
}

Unfortunately that doesnt work. I am having error:

Cannot implicitly convert type 'app.ManagerService<app.FooManager>' to 'app.ManagerService<T>' .

What is wrong there?

When calling GetService<T>(string serviceName) , the generic type T must be known at compile time . Either you need to specify it when making the call, or the compiler must be able to derive it from the parameters of the method (but you have no such parameters).

You can fix that by eliminating serviceName and using T instead, like this:

static ManagerService<T> GetService<T>() where T : BaseManager
{
    return new ManagerService<T>();
}

static void Main(string[] args)
{
    var s1 = GetService<FooManager>();     // returns ManagerService<FooManager>
    var s2 = GetService<AnotherManager>(); // returns ManagerService<AnotherManager>
}

If you don't know the T -s in advance (you only know serviceName at runtime) then you can use the abstract base type BaseManager :

abstract class ManagerService { }
class ManagerService<T> : ManagerService where T : BaseManager { }

static ManagerService GetService(string serviceName)
{
    switch(serviceName)
    {
        case "foo":
            return new ManagerService<FooManager>();
        case "another":
            return new ManagerService<AnotherManager>();
    }
    throw new ArgumentException("Service not found");
}

static void Main(string[] args)
{
    var s1 = GetService("foo");     // returns ManagerService<FooManager> typed as ManagerService
    var s2 = GetService("another"); // returns ManagerService<AnotherManager> typed as ManagerService
}

Generally, as a consumer of generics, you should know the types you're working with at compile time - they're either a specific type you know and can pass directly as a generic type parameter or you're implementing something generic yourself and are passing through some type arguments.

You're trying to mix in runtime knowledge. This tends to be fragile even if you can make it work, and will tend to involve lots of excessive casting to "shut up" the compiler - even though it's warnings are valid and you're trading compile time safety for (possible) runtime errors.

So, if possible, try not to "defer" generic decisions until runtime. This should compile:

class ManagerService<T> where T : BaseManager
{

}

static ManagerService<T> GetService<T>(/* not needed? */string serviceName)  where T : BaseManager
{
    return new ManagerService<T>();
}

Because now we're letting the generic parameter determine our return type. Of course, at this point it's unclear if the string serves any purpose.

Even with your original code, you'd still have had to supply the correct type parameter at the callsite in order for it to compile, as you will with the above.

Since you commented that you want to determine the type of manager based on a string you get from the database:

If you don't know the type of manager at compile time, then maybe you want to go with this dependency injection approach instead:

(If this is going to work depends on how your BaseManager and ManagerService are implemented.)

// common interface for all manager implementations
interface IManager
{ }

// optional: basic implementation
abstract class BaseManager : IManager
{ }

class FooManager : BaseManager
{ }

class AnotherManager : BaseManager
{ }

class ManagerService
{
    // constructor accepting the IManager implementation
    public ManagerService(IManager manager);
}

static ManagerService GetService(string serviceName)
{
    switch(serviceName)
    {
        case "foo":
            return new ManagerService(new FooManager());
        case "another":
            return new ManagerService(new AnotherManager());
    }
    throw new ArgumentException("Service not found");
}

static void Main(string[] args)
{
    var serviceBase = GetService("foo"); // Returns ManagerService using the FooManager implementation
    var serviceAnother = GetService("another"); // Returns ManagerService using the AnotherManager implementation
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM