Brandon Brandon - 2 months ago 13
C# Question

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

I'm trying to use the

Mono.CSharp
NuGet package to evaluate an expression on an instance of a class. I saw this answer on this question but it doesn't specifically say if I'm able to use it for instances, just that it can evaluate general expressions.

Here is my wrapping of the evaluator code:

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


Here's the object who's properties/fields I want to get:

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...";
}


I use a simple console application to consume the wrapper:

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


If I make a
public static
instance of the
Bar
class, I can view its properties from the console:

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


enter image description here

But I don't want a static instance of an object. I just want to pass a pre-instanced object and use its properties.

If I instantiate the object locally (or at the class level) and try to evaluate it, the following is the outcome:

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


enter image description here

Update:

I found
quake-console
with a Roslyn option on Github
which provides a
AddVariable
method but it seems very heavy for what I'm trying to do, DirectX and MonoGame etc... If there's such a method somehow in Mono.CSharp.....

Evk Evk
Answer

Not sure what instance object you mean - you don't have any instances of Bar in provided code (I think). However, you can evaluate properties of instance object, but you need to create it first. Run your console application and type this (in console):

var b = new ConsoleApplication1.Bar();

Where ConsoleApplication1 is namespace of your Bar class. Now you can evaluate expressions on this instance variable:

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

If you don't want user to create this object - just evaluate string "var b = new ConsoleApplication1.Bar();" yourself, before Console.ReadLine() statement.

UPDATE. It is possible to import external variable to the scope of evaluator, but this requires some reflection, because API designers for some reason did not expose this. See the code below:

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

Then in your Main method, pass your instance "Bar" object like this:

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