如何根据字符串动态调用类?

Posted

技术标签:

【中文标题】如何根据字符串动态调用类?【英文标题】:How to dynamically invoke a class based on a string? 【发布时间】:2020-04-09 15:38:26 【问题描述】:

您能否给我一些指导以根据名称动态加载类。该名称在运行时作为参数提供。

    static void Main(string[] args) 
    
        ...
     

假设我的论点是“鹦鹉”,那么我想加载我的 parrotProcessor 类。如果我的参数名称是“snake”,那么我会加载我的 To make snakeProcessor。当然,这意味着我有一个继承接口 IProcessor 的鹦鹉和蛇处理器类。我不知道所有处理器的列表是什么。此列表由其他开发人员维护,他们可以创建他们想要的内容。

我的处理器接口示例:

    public interface IProcessor
    
        void Process();
    

在我的 Program.cs 中

    static void Main(string[] args) 
    
        var processor = GetProcessor(args[0]);
        processor.Process();
     

我的问题是我应该在GetProcessor() 方法中做什么?

这是我此刻所拥有的:

    private IProcessor GetProcessor(string name)
    
        switch (name)
        
             case "ant":
                return new AntProcessor();
             case "parrot":
                return new ParrotProcessor();
             case "snake":
                return new SnakeProcessor();
             default:
                throw new ArgumentException("Case not found");
        
    

但这意味着我必须在每次创建新的处理器专用类时更新这个丑陋的开关。这不可能。我现在可以进行开发,但不能长期使用。

有什么解决办法?我应该在 NInject 中使用类似于 DI 的东西还是混合所有东西?我可以简单地使用 Invoke 方法吗?为什么 Invoke 或 DI?我知道这个问题有点悬而未决,但它在很多人的代码中经常发生,所以我想有关于它的独特的最佳实践。

【问题讨论】:

【参考方案1】:

你可以使用类似下面的东西

var type = Type.GetType("MyFullyQualifiedTypeName");
var myObject = (MyAbstractClass)Activator.CreateInstance(type);

您需要进行一些字符串修改,例如获取单词、添加处理器字符串并确保所有处理器都在同一个位置。

如果您确定该类型在当前程序集中,只需按此完全限定名称

 Activator.CreateInstance(Type.GetType("SomeProcessor"));

【讨论】:

【参考方案2】:

您可以尝试迭代特定程序集中的所有导出类型:

using System;
using System.Linq;
using System.Reflection;

namespace ConsoleApp

    public class Program
    
        public static void Main (string[] args)
        
            Assembly assembly = typeof (Program).Assembly; // use current assembly...

            var types = assembly.GetExportedTypes() // public types only
                                .Where (type => type.GetInterfaces().Contains (typeof (IProcessor))) // interface must be implemented
                                .Where (type => type.Name.EndsWith ("Processor")) // and maybe use some naming convention?
                                .ToList();

            //string name = args[0];
            string name = "Parrot";

            Type parrotType = types.Where (x => x.Name.StartsWith (name)).FirstOrDefault();

            if (parrotType != null)
            
                // it will work only when we implement parameterless constructor for this type
                IProcessor parrotInstance = (IProcessor) Activator.CreateInstance (parrotType);
                parrotInstance.Process();
            
        
    

    public interface IProcessor
    
        void Process();
    

    public class SnakeProcessor : IProcessor
    
        public void Process()
        
        
    

    public class ParrotProcessor : IProcessor
    
        public void Process()
        
            Console.WriteLine ("Parrot Process");
        
    

【讨论】:

【参考方案3】:

首先,使用switch 块绝对没有问题。它完成了工作,并且在命令行参数的解释方面充当了明确的事实来源。它还充当白名单,防止用户传递您可能不希望他们实例化的类名。是的,每次添加新类时都必须更新它,但您已经添加了类本身,因此您已经处于需要新版本和部署的情况。

话虽如此,您希望能够查找命令行参数并自动知道要实例化哪种类型的原因有很多。这对Dictionary<string,Func<IProcessor>> 来说是微不足道的:

this.Map = new Dictionary<string,Func<IProcessor>>

     "ant", () => new AntProcessor() ,
     "snake", () => new SnakeProcessor() ,
     "parrot", () => new ParrotProcessor() ,
     "lizard", () => new LizardProcessor() 
;

然后你会像这样处理一个命令行参数:

//Use the string to look up a delegate
var ok = this.Map.TryGetValue(textFromCommandline, out var func);  

//If not found, user entered a bad string
if (!ok) throw new ArgumentException();

//Invoke the delegate to obtain a new instance 
IProcessor processor = func(); 

return processor;

一旦你理解了以这种方式使用地图的概念,你就可以想出一个方案来自动填充它,例如

this.Map = assembly.GetTypes()
    .Where( t => typeof(IProcessor).IsAssignableFrom( t ))
    .ToDictionary
    (
        t => t.Name, 
        t => new Func<IProcessor>( () => Activator.CreateInstance(t) as IProcessor );
    );

您将实现不必在任何地方维护硬编码列表的目标。

【讨论】:

typeof(IProcessor).IsAssignableFrom(typeof(IProcessor)) == true【参考方案4】:

您可以像这样使用反射和 LINQ 的少许组合来重写 private IProcessor GetProcessor(string name) 方法:

 private IProcessor GetProcessor<Tinterface>(string name) where Tinterface : IProcessor
 
      var type = typeof(Tinterface).Assembly.GetTypes()
                                   .First(x => x.FullName.Contains("name"));
      return (Tinterface)Activator.CreateInstance(type);
 

用法:

static void Main(string[] args) 

    var processor = GetProcessor<IProcessor>(args[0]);
    processor.Process();

这条路线为您节省了键入类的完全限定名称的压力

【讨论】:

我要添加GetTypes().Where(t =&gt; typeof(Tinterface).IsAssignableFrom(t)) 那个添加看起来很有趣。 @JeremyLakeman 我很想知道这将如何改进方法。 它会阻止尝试创建任何随机类,这可能会在它的构造函数中做一些意想不到的事情,然后再遇到强制转换异常。另外,您可能应该将条件更改为.First(x =&gt; x.Name == name)【参考方案5】:

我将添加建议使用反射的答案:EX:IProcessor parrotInstance = (IProcessor) Activator.CreateInstance(parrotType); 是将这部分代码制作为处理器的单独工厂类,这样您就可以在代码的其他地方重用工厂类,甚至如果您决定保留switch 语句,则工厂类的更改只会影响它,而不会影响任何依赖代码。

public interface IProcessorFactory

    IProcessor GetProcessor(string processorTypeName);

好读: SOLID Design Principles

【讨论】:

以上是关于如何根据字符串动态调用类?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据名称动态调用方法? [复制]

vb.net如何动态调用WebService接口啊

python 动态调用函数

如果类名作为字符串传递,如何动态调用类的方法

如何在派生类上动态调用静态方法

如何通过名称动态调用对象上的函数? [复制]