简体   繁体   English

使用泛型将接口类型映射到类

[英]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. 我的应用程序中有几个Get____Factory方法,我想将它们与泛型合并,但我仍在调整C#,并且a)不是100%确定泛型是正确的方法,并且b)仍在学习C#的处理方式仿制药。

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. 注意:最终将有一个字典或将接口类型映射到其实现的方法-if / else条件是丑陋且暂时的,而仅仅是一种测试方法。

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. 由于Cannot implicitly convert type 'RecipeFactory' to 'T'失败,因此这将不起作用。 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. 根据其他答案,我尝试了双InvalidCastException: Cannot cast from source type to destination type.(T) (object) ),但是InvalidCastException: Cannot cast from source type to destination type.出现此错误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 . 由于该方法返回T您将在出站时将对象强制转换为T To do this cast you will have to make factory an IFactory 为此,您必须将factoryIFactory

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. 首先让我说,您实际上正在看的是控制反转(IOC)框架的简单版本。 Take a look at Ninject or something similar because it's kernel and binding factory are pretty much exactly what you want. 看一下Ninject或类似的东西,因为它的内核和绑定工厂几乎正是您想要的。 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. 它甚至允许附加元数据,因此您可以根据情况将同一接口解析为不同的实现,这在您有可能需要从Web数据源或缓存数据源中提取数据层时非常有用,例如。 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. 大多数IOC框架还提供递归依赖关系解析,这意味着当某些实例的构造函数需要其他依赖关系时,基于可推断的映射或默认映射,相同的依赖关系解析会一直出现在整个链中。

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. 除此之外,要做自己想做的事情,您将需要使用Activator.CreateInstance ,它采用一个类型并根据该类型构造一个新实例。 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: 这是一个完全有效的示例(经过30秒钟的测试),尽我所能,它可以完成您想做的事情:

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. 这里的解决方案与SC的解决方案有点不同。

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>();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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