简体   繁体   中英

Casting to a base class using generics

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.

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