[英]Call non-generic function from a generic class stored in a Type variable at runtime
我不知道在標題中表達這個問題的最佳方式,所以如果它不是最好的,我深表歉意。
不過我相信這個解釋很容易理解。
我正在制作一個小的 C# 命令行外殼,並且我將每個命令(例如ls
、 cat
)作為一個單獨的類來實現。 我正在使用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
,並按照我認為合適的方式實現ParseOpts
和HandleParseError
。
到目前為止,這一切都說得通,但我不知道如何使用它。
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.