I seem to have some trouble understanding generics in c#.
Basically i have a base class called ConfigWorker
and a bunch of sub classes which should all use their own config class deriving from BaseConfig
.
The ConfigWorker class i want to use should be determined dynamically during runtime given the name of the class as a parameter.
I can instantiate the sub class given it's name, but no matter what i try, i can't get the casting to a sensible base class to work.
Here's my code:
namespace DocumentHandler
{
public class BaseConfig
{
}
public class ConfigWorker<T> where T : BaseConfig
{
public virtual void Work(T options)
{
}
}
public class Worker1 : ConfigWorker<Worker1.Config>
{
public class Config : BaseConfig
{
public string test = "";
}
public override void Work(Config options)
{
//do something
}
}
public class Worker2 : ConfigWorker<Worker2.Config>
{
public class Config : BaseConfig
{
public string test = "";
}
public override void Work(Config options)
{
//do something else
}
}
public class Test
{
public static BaseConfig config;
public static void test()
{
(Activator
.CreateInstance(Type.GetType("DocumentHandler.Worker2"))
as ConfigWorker<BaseConfig>)
.Work(config);
}
}
}
The crucial line is
(Activator
.CreateInstance(Type.GetType("DocumentHandler.Worker2"))
as ConfigWorker<BaseConfig>)
.Work(config);
The casting to ConfigWorker<BaseConfig>
returns null, as the cast can not be performed.
Trying to simply cast to ConfigWorker
does not compile as the type parameter is missing.
Anything else i can try? CreateInstance
obviously just returns an object and i need to cast that to be able to call the Work
method
Any help is appreciated.
An instance of Worker2 is not a ConfigWorker<BaseConfig>
! It's a ConfigWorker<Worker2.Config>
. These are two totally different types. Generic classes are invariant. Only interfaces and delegates can be co- or contra-variant.
In your example, ConfigWorker
is even contra-variant in T
, meaning you use T
as the type of an input parameter to a method. So what you try is actually dangerous.
Imagine your line would work: you get an variable of type ConfigWorker<BaseConfig>
, so you could rely on this instance having a method Work()
which takes a BaseConfig
(or something derived from it) as argument. So nothing could stop you from calling it like
worker.Work(new Worker1.Config());
Compiles fine. But wait a moment! Didn't your line state that worker
is a Worker2
? Worker2
instances can only handle Worker2.Config
arguments!
You completely loose type safety this way (well, you would if it was allowed).
There is a flaw in your class design.
This looks like a good problem that factory pattern has good good solution for. Here is a simplified solution
namespace DocumentHandler
{
public interface IBaseConfig
{
}
public class ConfiManager : IBaseConfig
{
}
public abstract class WorkerFactory
{
private readonly IBaseConfig _config;
protected WorkerFactory(IBaseConfig config)
{
this._config = config;
}
public virtual void Work()
{
}
}
public class Worker1 : WorkerFactory
{
private readonly IBaseConfig _config;
public Worker1(IBaseConfig config):base(config)
{
_config = config;
}
public string test = "";
public override void Work()
{
//do something
}
}
public class Worker2 : WorkerFactory
{
private readonly IBaseConfig _config;
public string test = "";
public Worker2(IBaseConfig config):base(config)
{
this._config = config;
}
public override void Work()
{
Console.WriteLine("Hello world");
}
}
public class Test
{
public static IBaseConfig config = new ConfiManager();
public static void test()
{
WorkerFactory worker =
(Worker2) Activator.CreateInstance(Type.GetType("DocumentHandler.Worker2"), config);
worker.Work();
}
}
}
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.