1

Just came across the latest build of Mono.CSharp and love the promise it offers.

Was able to get the following all worked out:

namespace XAct.Spikes.Duo
{
class Program
{
    static void Main(string[] args)
    {
        CompilerSettings compilerSettings = new CompilerSettings();
        compilerSettings.LoadDefaultReferences = true;
        Report report = new Report(new Mono.CSharp.ConsoleReportPrinter());

        Mono.CSharp.Evaluator e;

        e= new Evaluator(compilerSettings, report);

        //IMPORTANT:This has to be put before you include references to any assemblies
        //our you;ll get a stream of errors:
        e.Run("using System;");
        //IMPORTANT:You have to reference the assemblies your code references...
        //...including this one:
        e.Run("using XAct.Spikes.Duo;");

        //Go crazy -- although that takes time:
        //foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        //{
        //    e.ReferenceAssembly(assembly);
        //}
        //More appropriate in most cases:
        e.ReferenceAssembly((typeof(A).Assembly));

        //Exception due to no semicolon
        //e.Run("var a = 1+3");
        //Doesn't set anything:
        //e.Run("a = 1+3;");
        //Works:
        //e.ReferenceAssembly(typeof(A).Assembly);
        e.Run("var a = 1+3;");
        e.Run("A x = new A{Name=\"Joe\"};");

        var a  = e.Evaluate("a;");
        var x = e.Evaluate("x;");

        //Not extremely useful:
        string check = e.GetVars();

        //Note that you have to type it:
        Console.WriteLine(((A) x).Name);

        e = new Evaluator(compilerSettings, report);
        var b = e.Evaluate("a;");
    }
}
public class A
{
    public string Name { get; set; }
}
}

And that was fun...can create a variable in the script's scope, and export the value.

There's just one last thing to figure out... how can I get a value in (eg, a domain entity that I want to apply a Rule script on), without using a static (am thinking of using this in a web app)?

I've seen the use compiled delegates -- but that was for the previous version of Mono.CSharp, and it doesn't seem to work any longer.

Anybody have a suggestion on how to do this with the current version?

Thanks very much.

References: * Injecting a variable into the Mono.CSharp.Evaluator (runtime compiling a LINQ query from string) * http://naveensrinivasan.com/tag/mono/

Community
  • 1
  • 1
Ciel
  • 591
  • 4
  • 12

1 Answers1

0

I know it's almost 9 years later, but I think I found a viable solution to inject local variables. It is using a static variable but can still be used by multiple evaluators without collision.

You can use a static Dictionary<string, object> which holds the reference to be injected. Let's say we are doing all this from within our class CsharpConsole:

public class CsharpConsole {
   public static Dictionary<string, object> InjectionRepository {get; set; } = new Dictionary<string, object>();
}

The idea is to temporarily place the value in there with a GUID as key so there won't be any conflict between multiple evaluator instances. To inject do this:

public void InjectLocal(string name, object value, string type=null) {
    var id = Guid.NewGuid().ToString();
    InjectionRepository[id] = value;
    type = type ?? value.GetType().FullName;
    // note for generic or nested types value.GetType().FullName won't return a compilable type string, so you have to set the type parameter manually
    var success = _evaluator.Run($"var {name} = ({type})MyNamespace.CsharpConsole.InjectionRepository[\"{id}\"];");
   // clean it up to avoid memory leak
   InjectionRepository.Remove(id);
}

Also for accessing local variables there is a workaround using Reflection so you can have a nice [] accessor with get and set:

        public object this[string variable]
        {
            get
            {
                FieldInfo fieldInfo = typeof(Evaluator).GetField("fields", BindingFlags.NonPublic | BindingFlags.Instance);
                if (fieldInfo != null)
                {
                    var fields = fieldInfo.GetValue(_evaluator) as Dictionary<string, Tuple<FieldSpec, FieldInfo>>;
                    if (fields != null)
                    {
                        if (fields.TryGetValue(variable, out var tuple) && tuple != null)
                        {
                            var value = tuple.Item2.GetValue(_evaluator);
                            return value;
                        }
                    }
                }
                return null;
            }
            set
            {
                InjectLocal(variable, value);
            }
        }

Using this trick, you can even inject delegates and functions that your evaluated code can call from within the script. For instance, I inject a print function which my code can call to ouput something to the gui console window:

        public delegate void PrintFunc(params object[] o);

        public void puts(params object[] o)
        {
            // call the OnPrint event to redirect the output to gui console
            if (OnPrint!=null)
               OnPrint(string.Join("", o.Select(x => (x ?? "null").ToString() + "\n").ToArray()));
        }

This puts function can now be easily injected like this:

   InjectLocal("puts", (PrintFunc)puts, "CsInterpreter2.PrintFunc");

And just be called from within your scripts:

   puts(new object[] { "hello", "world!" });

Note, there is also a native function print but it directly writes to STDOUT and redirecting individual output from multiple console windows is not possible.

henon
  • 2,022
  • 21
  • 23