我可以使用 Mono.CSharp.dll 来计算 class 实例上的表达式吗?

Can I use Mono.CSharp.dll to evaluate expressions on an instance of a class?

我正在尝试使用 Mono.CSharp NuGet 包来计算 class 实例上的表达式。我看到 this answer on this question 但它没有具体说明我是否能够将它用于实例,只是它可以评估一般表达式。

这是我对评估程序代码的封装:

public class Foo
{
    private Evaluator _evaluator;
    private StringBuilder _errors;

    public Foo()
    {

        this._errors = new StringBuilder();
        var tw = new StringWriter(this._errors);

        var ctx = new CompilerContext(new CompilerSettings()
        {
            AssemblyReferences = new List< string>
            {
                Assembly.GetExecutingAssembly().FullName
            }

        }, new StreamReportPrinter(tw));

        var eval = new Evaluator(ctx)
        {
            InteractiveBaseClass = typeof( Bar ) //Do I need this???? I don't know what it does...
        };


        string usings = @"
        using System;
        using System.Drawing; 
        using System.Collections.Generic;
        using System.Linq; ";

        eval.Run(usings);
        this._evaluator = eval;
    }

    public string Evaluate(string expression)
    {
        try
        {
            object result;
            bool results;


            this._errors.Clear();

            this._evaluator.Evaluate(expression, out result, out results);

            if ( results )
            {
                return result.ToString();
            }

            if ( this._errors.Length > 0 )
            {
                return this._errors.ToString();
            }
        }
        catch ( Exception ex )
        {
            return ex.Message;
        }
        return "";
    }
}

这是properties/fields我想得到的对象:

public class Bar
{
    public string Joke = "How do you say 3 cats drown in French?";
    public string Punchline => "Trois Cats sank!";
    public string Drumroll = "Ba dum tsss...";
}

我使用一个简单的控制台应用程序来使用包装器:

private static void Main(string[] args)
{
    var foo = new Foo();
    string input = Console.ReadLine();

    while (!string.IsNullOrEmpty(input)) 
    {
        Console.WriteLine(foo.Evaluate(input));
        input = Console.ReadLine();
    }

    Console.WriteLine("Press any key to quit");
    Console.Read();
}

如果我创建 Bar class 的 public static 实例,我可以从控制台查看其属性:

public static Bar b;
private static void Main(string[] args)
{
    b = new Bar();
    .        
    .        
    .        
}

但我不想要对象的静态实例。我只想传递一个预先实例化的对象并使用它的属性。

如果我在本地(或在 class 级别)实例化对象并尝试对其求值,结果如下:

private static void Main(string[] args)
{
    var b = new Bar();

    var foo = new Foo();
    string input = Console.ReadLine();

    while (!string.IsNullOrEmpty(input)) 
    {
        Console.WriteLine(foo.Evaluate(input));
        input = Console.ReadLine();
    }

    Console.WriteLine("Press any key to quit");
    Console.Read();
}

更新:

我发现 quake-console with a Roslyn option on Github 提供了 AddVariable 方法,但对于我正在尝试做的事情、DirectX 和 MonoGame 等来说,它似乎非常繁重...如果在 Mono.CSharp.....

不确定您指的是什么实例对象 - 您在提供的代码中没有任何 Bar 实例(我认为)。但是,您可以评估实例对象的属性,但您需要先创建它。 运行 您的控制台应用程序并输入(在控制台中):

var b = new ConsoleApplication1.Bar();

其中 ConsoleApplication1 是您的 Bar 的命名空间 class。现在您可以计算此实例变量上的表达式:

b.Joke // outputs How do you say 3 cats drown in French?

如果您不希望用户创建此对象 - 只需在 Console.ReadLine() 语句之前自己评估字符串 "var b = new ConsoleApplication1.Bar();"。

更新。可以将外部变量导入评估器的范围,但这需要一些反思,因为 API 设计师出于某种原因没有公开这一点。请看下面的代码:

public class Foo
{
    private Evaluator _evaluator;
    private StringBuilder _errors;

    public Foo(object context, string contextName) {

        this._errors = new StringBuilder();
        var tw = new StringWriter(this._errors);

        var ctx = new CompilerContext(new CompilerSettings() {
            AssemblyReferences = new List<string> {
                Assembly.GetExecutingAssembly().FullName
            }

        }, new StreamReportPrinter(tw));
        var eval = new Evaluator(ctx);
        string usings = @"
    using System;
    using System.Drawing; 
    using System.Collections.Generic;
    using System.Linq; ";
        eval.Run(usings);

        object result;
        bool results;
        // here we initialize our variable, but set it to null
        var constructor = $"{context.GetType().FullName} {contextName} = null;";
        eval.Evaluate(constructor, out result, out results);
        // here we use reflection to get private field which stores information about available variables
        FieldInfo fieldInfo = typeof(Evaluator).GetField("fields", BindingFlags.NonPublic | BindingFlags.Instance);
        var fields = (Dictionary<string, Tuple<FieldSpec, FieldInfo>>) fieldInfo.GetValue(eval);
        // and we set variable we just created above to the "context" external object
        fields[contextName].Item2.SetValue(eval, context);

        this._evaluator = eval;
    }

    public string Evaluate(string expression)
    {
        try
        {
            object result;
            bool results;
            this._errors.Clear();

            this._evaluator.Evaluate(expression, out result, out results);

            if (results)
            {
                return result.ToString();
            }

            if (this._errors.Length > 0)
            {
                return this._errors.ToString();
            }
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
        return "";
    }
}

然后在您的 Main 方法中,像这样传递您的实例 "Bar" 对象:

private static void Main(string[] args)
{
        var bar = new Bar();
        // here you define the name of new variable ("b")
        var foo = new Foo(bar, "b");
        string input = Console.ReadLine();

        while (!string.IsNullOrEmpty(input))
        {
            Console.WriteLine(foo.Evaluate(input));
            input = Console.ReadLine();
        }

        Console.WriteLine("Press any key to quit");
        Console.Read();
}