简体   繁体   English

在运行时从存储在 Type 变量中的泛型类调用非泛型函数

[英]Call non-generic function from a generic class stored in a Type variable at runtime

I don't know the best way to phrase this question in the title, so I apologise if it's not the best.我不知道在标题中表达这个问题的最佳方式,所以如果它不是最好的,我深表歉意。
However I believe the explanation is easy to follow.不过我相信这个解释很容易理解。

I am making a small C# command line shell, and I am implementing each command (eg ls , cat ) as a separate class.我正在制作一个小的 C# 命令行外壳,并且我将每个命令(例如lscat )作为一个单独的类来实现。 I am using the CommandLineParser library to parse command line options.我正在使用CommandLineParser库来解析命令行选项。

Here is my code:这是我的代码:

//Command.cs
public abstract class Command<T>
{
    public void Run(string[] args)
    {
        Parser.Default.ParseArguments<T>(args)
            .WithParsed(ParseOpts)
            .WithNotParsed(HandleParseError);
    }

    public abstract void ParseOpts(T opts);
    public abstract void HandleParseError(IEnumerable<Error> errs);
}

//Cat.cs
public class Cat : Command<CatOptions>
{
    //...
    public override void ParseOpts(CatOptions opts)
    {
        //...
    }

    public override void HandleParseError(IEnumerable<Error> errs)
    {
        //...
    }
    //other stuff...
}

//CatOptions.cs
class CatOptions
{
    [Option('E', "show-ends", Default = False, 
        HelpText = "display $ at end of each line")]
    public bool ShowEnds { get; set; }

    //other options...
}

The way the parser works is that I have to call the boilerplate code that is in Run (ie ParseArguments ), which will parse the options in args based on the options specified in the options class T .解析器的工作方式是我必须调用Run的样板代码(即ParseArguments ),它将根据选项类T指定的选项解析args中的选项。 If parsing is successful, it will call ParseOpts , where the command can access the options and do stuff, or HandleParseError if it fails.如果解析成功,它将调用ParseOpts ,命令可以在其中访问选项并执行操作,如果失败则调用HandleParseError

I want it so that I don't have to repeat this boilerplate code, but rather just specify the relevant options type T , and implement ParseOpts and HandleParseError as I see fit.我想要它,这样我就不必重复这个样板代码,而只是指定相关选项类型T ,并按照我认为合适的方式实现ParseOptsHandleParseError

So far, this all makes sense, but I don't know to use it as I want to.到目前为止,这一切都说得通,但我不知道如何使用它。

public class Shell
{
    //What do I put here?
    private Dictionary<String, ???> Commands = new Dictionary<String, ???>
    {
        {"cat", Cat}, // or new Cat()? typeof(Cat)?
        //other commands...
    };

    //other stuff...

    private void Execute()
    {
        string input = Console.ReadLine();
        string[] args = ParseInput(input);

        string cmd = args[0];

        if (Commands.ContainsKey(cmd))
        {
            //What I want to be able to do
            Commands[cmd].Run(args);
        }
        else
        {
            //...
        }
    }
    //...
}

In my main Shell class, I have a Dictionary Commands which I use to map command names to their classes.在我的主要Shell类中,我有一个字典Commands ,用于将命令名称映射到它们的类。 What I want to be able to do is simply get input from the user, parse the command from the input, check if it's in the dictionary, and then Run that command, but that's where I am stuck.我想要做的就是简单地从用户那里获取输入,从输入中解析命令,检查它是否在字典中,然后Run该命令,但这就是我被卡住的地方。

I can't declare Commands as something like Dictionary<String, Command<T>> , because I have to specify what T is.我不能将Commands声明为Dictionary<String, Command<T>> ,因为我必须指定T是什么。 I don't know if it's possible to somehow declare Commands with a generic generic type or something like that.我不知道是否有可能以某种方式声明具有通用泛型类型或类似类型的Commands

I also tried storing the Commands as Type , and then instantiating them at runtime and calling Run ;我还尝试将Commands存储为Type ,然后在运行时实例化它们并调用Run like this:像这样:

private Dictionary<String, Type> Commands = new Dictionary<String, Type>
    {
        {"cat", typeof(Cat)},
        //other commands...
    };

But I don't know how to instantiate the class and call Run in Execute because I need to somehow cast it to a Command<T> and specify the appropriate T at runtime:但我不知道如何实例化该类并在Execute调用Run ,因为我需要以某种方式将其强制转换为Command<T>并在运行时指定适当的T

if (Commands.ContainsKey(cmd))
{
    //Want to do something like this, but obviously it doesn't work
    //because I have to provide the T type
    var cmdType = Commands[cmd];
    ((Command<T>)Activator.CreateInstance(cmdType)).Run(args);
}

At runtime I know that regardless of what class it is they are all of type Command<T> , and I can determine the actual class and type T based on the command name (eg cat maps to CatOptions ).在运行时,我知道无论是什么类,它们都是Command<T>类型,我可以根据命令名称(例如cat映射到CatOptions )确定实际的类和类型T

I know I can do explicit casting like this with if statements:我知道我可以使用 if 语句进行这样的显式转换:

if (cmdType == typeof(Cat))
{
  ((Cat)Activator.CreateInstance(cmdType)).Run(args);
}
else if ...

But I don't want to do that because it will repeat code (unless there's a smart way to do it without repeating code?)但我不想这样做,因为它会重复代码(除非有一种不重复代码的聪明方法?)

What I want is for Shell to not have to know about any of the *Options classes, and for the *Options classes to all be encapsulated within each respective Command class.我想要的是Shell不必知道任何*Options类,并且将*Options类全部封装在每个相应的Command类中。 I know it's possible that my design is just terrible and there's a blatantly simple way of doing this;我知道我的设计可能很糟糕,而且有一种非常简单的方法可以做到这一点; if so please show me.如果是这样,请告诉我。

I know that there must be some way to do it with reflection, but I have been having difficulty figuring out how to do it.我知道一定有某种方法可以通过反射来做到这一点,但我一直很难弄清楚如何去做。

What can I do in order to get the runtime polymorphism that I desire?我该怎么做才能获得我想要的运行时多态性?

Introduce a non generic abstraction引入非泛型抽象

public interface ICommandLine {
    void Run(string[] args);
}

that the generic abstraction can be derived from泛型抽象可以从

public abstract class Command<T>: ICommandLine {

    public void Run(string[] args) {
        Parser.Default.ParseArguments<T>(args)
            .WithParsed(ParseOpts)
            .WithNotParsed(HandleParseError);
    }

    protected abstract void ParseOpts(T opts);
    protected abstract void HandleParseError(IEnumerable<Error> errs);
}

Allowing for a simpler implementation允许更简单的实现

//...

private Dictionary<String, ICommandLine> Commands = new Dictionary<String, ICommandLine> {
    {"cat", new Cat()},

    //other commands...
};

//...

I would add an interface and you can add this instance in dictionary我会添加一个接口,你可以在字典中添加这个实例

public interface ICommand
{
 void Run(string[] args);
}
//Command.cs
public abstract class Command<T> : ICommand
{
    public void Run(string[] args)
    {
        Parser.Default.ParseArguments<T>(args)
            .WithParsed(ParseOpts)
            .WithNotParsed(HandleParseError);
    }

    public abstract void ParseOpts(T opts);
    public abstract void HandleParseError(IEnumerable<Error> errs);
}

//Cat.cs
public class Cat : Command<CatOptions>
{
    //...
    public override void ParseOpts(CatOptions opts)
    {
        //...
    }

    public override void HandleParseError(IEnumerable<Error> errs)
    {
        //...
    }
    //other stuff...
}

//CatOptions.cs
class CatOptions
{
    [Option('E', "show-ends", Default = False, 
        HelpText = "display $ at end of each line")]
    public bool ShowEnds { get; set; }

    //other options...
}

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

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