在C#中,我们拥有漂亮的diff
类,它允许我们从源代码动态创建一些exe文件,运行它等等。
问题在于我们没有这样的来源。例如,我有一个委托(例如CodeDomProvider
),我想编译一个exe,它从命令行获取第一个参数,并用它执行传递Action<string>
。在更复杂的情况下,我有Action<string>
,它接受命令行参数,执行某些操作并在标准输出中写入内容。
我想要Func<string[], string>
之类的东西,它编译一个exe,用提供的参数运行它,从标准输出中获取结果,然后将其作为结果返回。理想情况下,它应该更通用,例如使用string ExecuteOutOfProcess(Func<string[], string> func, string[] args)
,它应该在内部取消并序列化所有内容,透明地调用代码。
有什么东西可以实现吗?因为做类似事情的唯一方法就是编写一个反编译器,然后从委托中获取C#源代码,然后使用CodeDomProvider的那些源代码,再次解析源代码......没有办法直接在编译器中传递表达式?..
答案 0 :(得分:3)
这实际上非常棘手。但是如果确保以某种方式使用它,你可以简化它:
在这些约束中,您可以使用一个简单的技巧:发送您尝试执行的任何类型和方法名称,并且您的帮助程序测试运行器可以使用程序集限定名称加载该类型并调用该方法。要发送所需的数据,您可以使用类似WCF或内存映射文件的内容,例如。
一个非常简单(且脆弱)的例子:
public static async Task<T> Run<T>(Func<T> func)
{
var mapName = Guid.NewGuid().ToString();
using (var mapFile = MemoryMappedFile.CreateNew(mapName, 65536))
{
using (var stream = mapFile.CreateViewStream())
using (var bw = new BinaryWriter(stream))
{
bw.Write(func.Method.DeclaringType.AssemblyQualifiedName);
bw.Write(func.Method.Name);
if (func.Target == null)
{
bw.Write(0);
}
else
{
using (var ms = new MemoryStream())
{
new BinaryFormatter().Serialize(ms, func.Target);
var data = ms.ToArray();
bw.Write(data.Length);
bw.Write(data);
}
}
}
using (var process = Process.Start(new ProcessStartInfo("LambdaRunner", mapName) { UseShellExecute = false, CreateNoWindow = true }))
{
process.EnableRaisingEvents = true;
await process.WaitForExitAsync();
switch (process.ExitCode)
{
case -10: throw new Exception("Type not accessible.");
case -11: throw new Exception("Method not accessible.");
case -12: throw new Exception("Unexpected argument count.");
case -13: throw new Exception("Target missing.");
case 0: break;
}
}
using (var stream = mapFile.CreateViewStream())
{
return (T)(object)new BinaryFormatter().Deserialize(stream);
}
}
}
helper runner可执行文件如下所示:
static int Main(string[] args)
{
var mapName = args[0];
using (var mapFile = MemoryMappedFile.OpenExisting(mapName))
{
string typeAqn;
string methodName;
byte[] target;
using (var stream = mapFile.CreateViewStream())
using (var br = new BinaryReader(stream))
{
typeAqn = br.ReadString();
methodName = br.ReadString();
target = br.ReadBytes(br.ReadInt32());
}
var type = Type.GetType(typeAqn);
if (type == null) return -10;
var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.InvokeMethod);
if (method == null) return -11;
if (method.GetParameters().Length > 0) return -12;
object returnValue;
if (target.Length == 0)
{
if (!method.IsStatic) return -13;
returnValue = method.Invoke(null, new object[0]);
}
else
{
object targetInstance;
using (var ms = new MemoryStream(target)) targetInstance = new BinaryFormatter().Deserialize(ms);
returnValue = method.Invoke(targetInstance, new object[0]);
}
using (var stream = mapFile.CreateViewStream())
new BinaryFormatter().Serialize(stream, returnValue);
return 0;
}
}
使用示例:
static string HelloWorld1()
{
return "Hello world!";
}
static async Task RunTest<T>(int num, Func<Task<T>> func)
{
try
{
Console.WriteLine($"Test {num}: {await func()}");
}
catch (Exception ex)
{
Console.WriteLine($"Test {num} failed: {ex.Message}");
}
}
[Serializable]
public struct Fun
{
public string Text;
public int Number;
public override string ToString() => $"{Text} ({Number})";
}
static async Task MainAsync(string[] args)
{
await RunTest(1, () => Runner.Run(HelloWorld1));
await RunTest(2, () => Runner.Run(() => "Hello world from a lambda!"));
await RunTest(3, () => Runner.Run(() => 42));
await RunTest(4, () => Runner.Run(() => new Fun{Text = "I also work!", Number = 42}));
}
如果您能够将自己置于我所概述的限制之内,这将非常有效 - 只需确保添加正确的错误处理。遗憾的是,没有简单的方法可以确保您尝试调用的函数是“纯粹的” - 如果某个地方存在依赖某些静态的情况,那么它就不会正常工作(也就是说,它不会使用静态状态)你的过程,但无论如何都有自己的过程。
在你的情况下,你必须决定这种方法是否值得。它可能会使事情变得更简单,它可能会使它们更糟糕:)。
答案 1 :(得分:0)
您可以将表达式转换为表达式树:
https://blogs.msdn.microsoft.com/charlie/2008/01/31/expression-tree-basics/
然后编译它:
https://msdn.microsoft.com/en-us/library/bb345362(v=vs.110).aspx
注意自己,你的表达最终可能会成为关闭。