简体   繁体   English

是否可以从采用字符串的工厂方法实例化泛型类型?

[英]Is it possible to instantiate a generic type from a factory method that takes a string?

I am writing an application that consists of an Importer and a Processor. 我正在编写一个由导入程序和处理器组成的应用程序。 The Importer will take a type and instantiate a List<> of that type. 导入器将采用一种类型并实例化该类型的List<> The Processor needs to take that returned list. 处理器需要获取返回的列表。

For example, I want to call Importer<Organisation>.GetObjects() and have it return a List<Organisation> . 例如,我想调用Importer<Organisation>.GetObjects()并让它返回List<Organisation> Then pass the List to my OrganisationProcessor. 然后将列表传递给我的OrganisationProcessor。

Similarly, I want to call Importer<Address>.GetObjects() and have it return a List<Address> . 同样,我想调用Importer<Address>.GetObjects()并让它返回List<Address> Then pass the List to my AddressProcessor. 然后将列表传递给我的AddressProcessor。

I would like to put the creation of the Importer<> and *Processor into a factory. 我想将Importer<>和* Processor的创建放入工厂。 I have removed all code and produced an uncontaminated sample below. 我删除了所有代码,并在下面生成了一个未被污染的示例。 My attempts have been met with no success and may be down to topics like variance which I don't fully understand. 我的尝试没有成功,可能归结为我不完全理解的方方面面。

class Program
{
    static void Main(string[] args)
    {
        //What I am currently doing
        Importer<Organisation> importer = new Importer<Organisation>();
        List<Organisation> list = importer.GetObjects();
        OrganisationProcessor processor = new OrganisationProcessor();
        processor.Process(list);


        //***What I want to do*** 
        string name = "Organisation";  //Passed in from external source
        var importer = Factory.GetImporter(name);  //returns Importer<Organisation>
        var processor = Factory.GetProcessor(name); //returns OrganisationProcessor
        processor.Process(importer.GetObjects()); //.Process takes List<Organisation>
    }
}

public class Importer<T>
{
    public List<T> GetObjects()
    {
        return new List<T>();
    }
}


public class OrganisationProcessor
{
    public void Process(List<Organisation> objects)
    {
        //Do something
    }
}

public class AddressProcessor
{
    public void Process(List<Address> objects)
    {
        //Do something
    }
}

public class Organisation { }

public class Address { }

This should do the trick, note that your initial intention hasn't been changed, which is surprise even for me. 这应该可以解决问题,请注意您的最初意图没有改变,即使是我也很惊讶。 Also note that you have to add namespaces to type literals in Factory. 还要注意,您必须添加名称空间以在Factory中键入文字。 Using LINQ to find types is more flexible, but it's much readable to enter path to type with namespace in one line. 使用LINQ查找类型更加灵活,但是在一行中输入带有名称空间的类型的路径很容易理解。 I have executed this sample on project without namespaces and it worked fine. 我已经在没有命名空间的项目上执行了此示例,并且效果很好。

void Main()
{
    string name = "Organisation";  //Passed in from external source
    var importer = Factory.GetImporter(name);  //returns Importer<Organisation>
    var processor = Factory.GetProcessor(name); //returns OrganisationProcessor
    processor.Process(importer.GetObjects()); //.Process takes List<Organisation>
    Console.WriteLine (importer.GetObjects().GetType());
}

public static class Factory
{
    public static Importer GetImporter(string name)
    {           
            //add namespace to name here
        var desiredType = Type.GetType(name);

        return new Importer(desiredType);
    }
    public static Processor GetProcessor(string name)
    {
            //add namespace to name here
        var desiredType = Type.GetType(name+"Processor");

        return (Processor)Activator.CreateInstance(desiredType);
    }
}

public class Importer 
{
    Type _typeOfEntities;

    public Importer(Type typeOfEntities)
    {
        _typeOfEntities = typeOfEntities;
    }
    public IList GetObjects()
    {
        Type generic = typeof(List<>);

        Type constructed = generic.MakeGenericType(new Type[] {_typeOfEntities});

        return (IList)Activator.CreateInstance(constructed);
    }
}
public interface Processor
{
    void Process(IList objects);
}

public class OrganisationProcessor : Processor
{
    public void Process(IList objects)
    {
        var desiredTypedObject = objects as List<Organisation>;
        if(desiredTypedObject != null)
        ProcessImp(desiredTypedObject);
    }
    private void ProcessImp(List<Organisation> objects)
    {
        Console.WriteLine ("processing Organisation");
    }
}

public class AddressProcessor
{
    public void Process(List<Address> objects)
    {
        //Do something by analogy
    }
}

public class Organisation { }

public class Address { }

The return types of the GetImporter and GetProcessor will have to be object . GetImporterGetProcessor的返回类型必须为object There is no way to have those methods use the run-time information in a string and return an object whose type is known at compile-time. 没有办法让这些方法在字符串中使用运行时信息并返回其类型在编译时已知的对象。

Your sample will have to look something like this: 您的样本必须看起来像这样:

string name = "Organisation";  //Passed in from external source
dynamic importer = Factory.GetImporter(name);  //returns Importer<Organisation>
dynamic processor = Factory.GetProcessor(name); //returns OrganisationProcessor
processor.Process(importer.GetObjects()); //.Process takes List<Organisation>

Second, the GetImporter method will be something like 其次,GetImporter方法将类似于

public static object GetImporter(string name)
{
    Type type = Type.GetType(name);
    Type importerType = typeof(Importer<>).MakeGenericType(type);
    return Activator.CreateInstance(importerType);
}

Last, the GetProcessor will be something like 最后,GetProcessor将类似于

public static object GetProcessor(string name)
{
    Type type = Type.GetType(name + "Processor");
    return Activator.CreateInstance(type);
}

Note that the string names passed in must be the full name of the type including namespace, and this is restricted to types (Organization, Address) that are in the same assembly as those methods. 请注意,传入的字符串名称必须是包括名称空间在内的类型的全名,并且仅限于与那些方法位于同一程序集中的类型(组织,地址)。 If you want to use types in other assemblies, you must work with the assembly qualified name , but then that string concatenation to produce the name of the processor type isn't going to work. 如果要在其他程序集中使用类型,则必须使用程序集限定名称 ,但是用于生成处理器类型名称的字符串串联将不起作用。

I would probably use a custom attribute on the processor types and then find all types in the assembly with the custom attribute instead of the simple string concatenation to produce the type name above. 我可能会在处理器类型上使用自定义属性,然后在具有自定义属性的程序集中找到所有类型,而不是通过简单的字符串串联来生成上面的类型名称。 The above scheme is brittle and could easily break if some of the types are renamed. 上面的方案很脆弱,如果重命名某些类型,很容易破坏。

Yes, it's possible. 是的,有可能。 Assumption is your classes are in the same assembly, this code below is not optimized much but will solve the problem: 假设您的类在同一程序集中,则下面的代码没有进行很多优化,但可以解决问题:

public static class Factory
{
    public static object GetImporter(string name)
    {
        var assembly = Assembly.GetExecutingAssembly();

        var type = assembly.GetTypes().Single(t => t.Name.Equals(name));
        var importType = typeof (Importer<>).MakeGenericType(type);

        return Activator.CreateInstance(importType);

    }

    public static object GetProcessor(string name)
    {
        var assembly = Assembly.GetExecutingAssembly();
        var type = assembly.GetTypes()
               .Single(t => t.Name.Equals(string.Format("{0}Processor", name)));

        return Activator.CreateInstance(type);
    }
}

So, you use Factory above like below: 因此,您可以像下面这样使用上方的Factory:

dynamic importer = Factory.GetImporter(name);
dynamic processor = Factory.GetProcessor(name);
processor.Process(importer.GetObjects());

Within your Factory the Get... Methodes could use the Activator.CreateInstance Method . 在工厂内,Get ...方法可以使用Activator.CreateInstance方法 This Method can create Instances of a class by assamby-name and type-name with additional parameters. 该方法可以通过具有附加参数的assamby-name和type-name创建类的实例。

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

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