OrcShell 是一個實驗性的 C# 互動式殼層,支援類型瀏覽、命名空間導航、成員檢視與動態代碼片段編譯執行。本文深入剖析其核心模組實作細節,展示如何利用反射與 CSharpCodeProvider 建構動態執行環境。
Context 初始化時掃描當前 AppDomain 所有已載入程序集,並透過 TypeManager 統一管理類型資訊。程式碼會過濾掉編譯器生成的 <PrivateImplementationDetails> 內部類型:
public Context() {
TypeManager = new TypeManager();
Assemblys = new Dictionary<String, Assembly>();
Assembly[] al = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in al) { AddAssembly(a); }
AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
}
private void AddAssembly(Assembly a) {
if (a != null) {
Assemblys.Add(a.FullName, a);
Type[] tl = a.GetTypes();
foreach (Type t in tl) {
if (!t.FullName.StartsWith("<PrivateImplementationDetails>"))
TypeManager.AddType(t);
}
}
}
void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args) {
Assembly a = args.LoadedAssembly;
if (!Assemblys.ContainsKey(a.FullName)) AddAssembly(a);
}
CdClassCmdHandler)支援相對/絕對路徑切換(cdc ..、cdc .、cdc NamespaceName),不支援級聯(cdc ../ns1/ns2)。輸入部分名稱時自動比對,唯一匹配則直接進入,否則列出所有匹配項。
ListClassCmdHandler)支援正則表達式過濾,由 TypeDictionary 實作列舉邏輯:
public void ListType(String match) {
Regex re = match != null ? new Regex(match) : null;
foreach (Type t in Types.Values) {
if (re != null && !re.IsMatch(t.Name)) continue;
Console.WriteLine("C:\t" + Context.EnsureAtLeastLength(t.Name, 20) + "\t" + t.FullName);
}
}
public void ListDir(String match) {
Regex re = match != null ? new Regex(match) : null;
foreach (TypeDictionary dic in SubTypeDictionary.Values) {
if (re != null && !re.IsMatch(dic.Name)) continue;
Console.WriteLine("N:\t" + Context.EnsureAtLeastLength(dic.Name, 20) + "\t" + dic.FullName);
}
}
利用 C# 擴充方法簡化呼叫語法,可直接對 Type 或 Object 執行個體呼叫 .methods()、.props()、.members()、.creaters():
public static class ClassExtensionMethods {
public static void methods(this Type t) {
foreach (MethodInfo mi in t.GetMethods()) Console.WriteLine(" " + mi);
}
public static void methods(this Object obj) { if (obj != null) methods(obj.GetType()); }
// props, members, creaters 同理...
}
CscCmdHandler)核心邏輯在 Run() 方法:將使用者輸入包裝在動態產生的類別 DynamicClass.MethodInstance 中,自動引入當前命名空間與常用命名空間,透過 CSharpCodeProvider 記憶體編譯並執行。
public override void Run() {
if (String.IsNullOrEmpty(InputCmdString)) return;
String fullCmd = String.Empty;
if (Context.TypeManager.Now != Context.TypeManager.Root)
fullCmd += " using " + Context.TypeManager.Now.FullName + ";";
fullCmd += @"
using System; using System.IO; using System.Text;
using System.Collections.Generic; using Orc.Shell.Core;
namespace Orc.Shell.Core.Dynamic {
public class DynamicClass {
public Orc.Shell.Core.Context Context;
public void Save(String name, Object obj) { Context.Save(name, obj); }
public Object My(String name) { return Context[name]; }
public void MethodInstance(Context context) { Context = context; " + InputCmdString + @"; }
}
}";
CompilerResults cr = Context.CodeProvider.CompileAssemblyFromSource(Context.CompilerParameters, fullCmd);
if (cr.Errors.HasErrors) { /* 輸出錯誤 */ }
else {
Assembly assem = cr.CompiledAssembly;
Object dynamicObject = assem.CreateInstance("Orc.Shell.Core.Dynamic.DynamicClass");
Type t = assem.GetType("Orc.Shell.Core.Dynamic.DynamicClass");
MethodInfo minfo = t.GetMethod("MethodInstance");
minfo.Invoke(dynamicObject, new Object[] { Context });
}
}
CodeProvider = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });
CompilerParameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll", "Orc.Shell.Core.dll", "OrcShell.exe" });
CompilerParameters.GenerateExecutable = false;
CompilerParameters.GenerateInMemory = true;
環境變數互動:Save(name, obj) 存入內容變數,My(name) 取出。另有 my 指令列出所有變數。
public static void p(this Object obj) { Console.WriteLine(obj); }
public static void P(this Object obj) { Console.WriteLine(obj); }
public static void print(this Object obj) { Console.WriteLine(obj); }
Alias.xml)可編輯專案目錄下的 Alias.xml 自訂指令縮寫,Alias 指令可查看目前定義。
OrcShell 雖為原型,但展示了利用 .NET 反射與動態編譯打造 REPL 環境的可行路徑,為進階診斷工具或教學輔助奠定基礎。