繁体   English   中英

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

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

我不知道在标题中表达这个问题的最佳方式,所以如果它不是最好的,我深表歉意。
不过我相信这个解释很容易理解。

我正在制作一个小的 C# 命令行外壳,并且我将每个命令(例如lscat )作为一个单独的类来实现。 我正在使用CommandLineParser库来解析命令行选项。

这是我的代码:

//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...
}

解析器的工作方式是我必须调用Run的样板代码(即ParseArguments ),它将根据选项类T指定的选项解析args中的选项。 如果解析成功,它将调用ParseOpts ,命令可以在其中访问选项并执行操作,如果失败则调用HandleParseError

我想要它,这样我就不必重复这个样板代码,而只是指定相关选项类型T ,并按照我认为合适的方式实现ParseOptsHandleParseError

到目前为止,这一切都说得通,但我不知道如何使用它。

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
        {
            //...
        }
    }
    //...
}

在我的主要Shell类中,我有一个字典Commands ,用于将命令名称映射到它们的类。 我想要做的就是简单地从用户那里获取输入,从输入中解析命令,检查它是否在字典中,然后Run该命令,但这就是我被卡住的地方。

我不能将Commands声明为Dictionary<String, Command<T>> ,因为我必须指定T是什么。 我不知道是否有可能以某种方式声明具有通用泛型类型或类似类型的Commands

我还尝试将Commands存储为Type ,然后在运行时实例化它们并调用Run 像这样:

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

但我不知道如何实例化该类并在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);
}

在运行时,我知道无论是什么类,它们都是Command<T>类型,我可以根据命令名称(例如cat映射到CatOptions )确定实际的类和类型T

我知道我可以使用 if 语句进行这样的显式转换:

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

但我不想这样做,因为它会重复代码(除非有一种不重复代码的聪明方法?)

我想要的是Shell不必知道任何*Options类,并且将*Options类全部封装在每个相应的Command类中。 我知道我的设计可能很糟糕,而且有一种非常简单的方法可以做到这一点; 如果是这样,请告诉我。

我知道一定有某种方法可以通过反射来做到这一点,但我一直很难弄清楚如何去做。

我该怎么做才能获得我想要的运行时多态性?

引入非泛型抽象

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

泛型抽象可以从

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

允许更简单的实现

//...

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

    //other commands...
};

//...

我会添加一个接口,你可以在字典中添加这个实例

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