简体   繁体   中英

Using generics to map interface types to classes

I have several Get____Factory methods in my application and I'd like to consolidate them with generics, but I'm still adjusting C# and a) am not 100% sure generics are the right way to go and b) am still learning how C# handles generics.

I'll eventually have a dictionary/map of factory interfaces and their classes. Not only do I want to consolidate all of my factories into an easy-access method but I need to allow plugin authors a way to register their own (and have access to them this way).

I started with something like this:

Note: Eventually there will be a dictionary or way to map interfaces types to their implementations - the if/else conditions are ugly and temporary, but simply a way to test.

public T GetFactory<T>() where T : IFactory {
    var t = typeof(T);

    if (t.Equals(typeof(IRecipeFactory))) {
        var factory = new RecipeFactory();
        return factory;
    }

    else if (t.Equals(typeof(IItemFactory))) {
        var factory = new ItemFactory();
        return factory;
    }

    else if (t.Equals(typeof(ITileFactory))) {
        var factory = new TileFactory();
        return factory;
    }
}

It fails with Cannot implicitly convert type 'RecipeFactory' to 'T' , so this won't work. In the long run I won't have conditionals but will rather lookup the class by its type. However, neither will work until I can find a solution for the cast issue.

Based on other answers, I tried double-casting ( (T) (object) ) but that errors with InvalidCastException: Cannot cast from source type to destination type. .

Either this is a poor architecture or I'm using the generics incorrectly.

You are going to want to cast the object to T on the way out since the method returns T . To do this cast you will have to make factory an IFactory

public T GetFactory<T>() where T : IFactory
{
    var t = typeof(T);

    if (t.Equals(typeof(IRecipeFactory)))
    {
        IFactory factory = new RecipeFactory();
        return (T)factory;
    }

    if (t.Equals(typeof(IItemFactory)))
    {
        IFactory factory = new ItemFactory();
        return (T)factory;
    }

    if (t.Equals(typeof(ITileFactory)))
    {
        IFactory factory = new TileFactory();
        return (T)factory;
    }

    throw new InvalidOperationException("Type not supported");
}

Let me first say that you are actually looking at is a simple version of an Inversion of Control (IOC) framework. Take a look at Ninject or something similar because it's kernel and binding factory are pretty much exactly what you want. It even allows the attachment of metadata so you can have the same interface resolve to different implementations depending on the circumstances, which is really useful when you have a data layer that might need to pull from either a web data source or a cache data source, for instance. Most IOC frameworks also offer recursive dependency resolution, which means when some instances have constructors that require other dependencies, the same dependency resolution occurs all the way down the chain based on the mappings or default mappings that can be inferred.

Aside from that, to do what you're after yourself, you'll want to make use of Activator.CreateInstance which takes a type and will construct a new instance based on that. You are on the right track with your dictionary mappings. When you tie those two together, you don't need any conditional logic and you don't need to know ahead of time or care about what type is being requested. When you're feeling comfortable you can actually shorten the dependency resolution and instantiation to a single line if you wish.

Here is a fully working sample (from my 30 seconds of testing) that does what you want to to the best of my understanding:

using System;
using System.Collections.Generic;

namespace Generics
{
    // create some dummy interfaces and implementations. 
    // make sure everything inherits from the same type to allow for 
    // a generic return statement
    public interface IFactory
    {
        void DoStuff();
    }
    public interface IFactory1 : IFactory { }
    public class Factory1 : IFactory1
    {
        public void DoStuff()
        {
            Console.WriteLine("Factory1");
        }
    }
    public interface IFactory2 : IFactory { }
    public class Factory2 : IFactory2
    {
        public void DoStuff()
        {
            Console.WriteLine("Factory2");
        }
    }


    class Program
    {
        // create our binding mappings
        IDictionary<Type, Type> bindings = new Dictionary<Type, Type>()
            {
                // expose a way for plugins/etc to add to this. that part is trivial.
                {typeof(IFactory1), typeof(Factory1) },
                {typeof(IFactory2), typeof(Factory2) }
            };

        // a method to actually resolve bindings based on expected types
        public IFactory ResolveBinding<T>() where T : IFactory
        {
            Type requestedType = typeof(T);
            if (requestedType != null && bindings.ContainsKey(requestedType))
            {
                // use the activator to generically create an instance
                return (T) Activator.CreateInstance(bindings[requestedType]);
            }

            return null;
        }

        // test it out
        static void Main(string[] args)
        {
            Program demo = new Program();
            // test with two interfaces
            demo.ResolveBinding<IFactory1>().DoStuff(); // prints out "Factory1"
            demo.ResolveBinding<IFactory2>().DoStuff(); // prints out "Factory2"
            Console.ReadKey();
        }
    }
}

Here is solution bit different from SC's solution.

public static class FactoryService
{
    private static readonly Dictionary<Type, Func<IFactory>> factories = new Dictionary<Type, Func<IFactory>>()
    {
        { typeof(IRecipeFactory), () => new RecipeFactory() },
        { typeof(IItemFactory), () => new ItemFactory() },
        { typeof(ITileFactory), () => new TileFactory() }
    };

    public static T GetFactory<T>() where T : IFactory
    {
        T factory = default(T);
        Type requestedType = typeof(T);

        if (factories.ContainsKey(requestedType))
        {
            factory = (T)factories[requestedType].Invoke();
        }

        return factory;
    }
}

public interface IFactory { }

public interface IRecipeFactory : IFactory { }

public interface IItemFactory : IFactory { }

public interface ITileFactory : IFactory { }

public class RecipeFactory : IRecipeFactory { }

public class ItemFactory : IItemFactory { }

public class TileFactory : ITileFactory { }

Then you use it like this:

IRecipeFactory rf = FactoryService.GetFactory<IRecipeFactory>();

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