C#, IronPython - import(?) from a non-static class - c#

I have a non-static C# class with some instance methods, which I need to call from IronPython scripts. Currently I'm doing it this way:
scope.SetVariable("class", instanceOfClass);
in C# code and
class.SomeMethod(args)
in script.
What I want is being able to call this class methods without adding class. each time in the script. Each script has its own instance of the class, and only one instance is used in one script.
If this class was static, the solution would be from ClassName import *, but as I know there is no similar construction for non-static classes.
How can this be done? I have some ideas (such as using reflection, or adding class. to each call in Python source programmatically), but they are overcomplicated and may be even not possible to implement.
UPD:
Problem solved by using such python code (before actual script):
def Method1(arg1): # for simple method
class.Method1(arg1)
def Method2(arg = 123): # for default values
class.Method2(arg)
def Method3(*args): # for params
class.Method3(args)
# so on

from ClassName import * is actually from namespace import type. This statement makes the type avaiable for use via the type name in Python. It makes no difference if the class is static or not. Consider this sample code - Environment being the static class.
import clr
from System import Environment
print Environment.CurrentDirectory
To solve your problem, inject a delegate to the class function into your ScriptScope, rather than the class itself.
Sample class
public class Foo {
public string GetMyString(string input) {
return input;
}
}
Usage
private static void Main(string[] args) {
ScriptEngine engine = Python.CreateEngine();
string script = "x = GetMyString('value')";
Foo foo = new Foo();
ScriptSource scriptSource = engine.CreateScriptSourceFromString(script);
ScriptScope scope = engine.CreateScope();
scope.SetVariable("GetMyString", new Func<string, string>(foo.GetMyString));
scriptSource.Execute(scope);
string output = scope.GetVariable<string>("x");
Console.WriteLine(output);
}
prints
value

Related

How to programmatically create a class library DLL using reflection?

Suppose my code possesses the knowledge about the metadata of a
nonexistent class library "mytest.dll", such as the types in this library, the functions of the types, the parameters and return types of the functions, etc.
How does my code manufacture this DLL using techniques such as reflection?
I know my code can generate the "mytest.cs" text file, then execute the compiler to produce the DLL, then delete the "mytest.cs" file. Just want to know if there are "more advanced" or "cooler" ways to do it.
Thanks.
There are 4 main steps in the process to compile and execute dynamic .net scripts from your application, even really complex scenarios can be simplified in this way:
Generate the code
Compile the script
Load the assembly
Execute the code
Lets generate a simple Hello Generated C# World App right now!:
Create a method that will generate an assembly that has 1 class called HelloWorldApp, this class has 1 method called GenerateMessage it will have X input parameters that will be integers, it will return a CSV string of the arguments that were passed in to it.
This solution requires the following package to be installed:
PM> Install-Package 'Microsoft.CodeAnalysis.CSharp.Scripting'
And will require the following using statements:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
Orchestration
The following method encapsulates the above steps:
private static void GenerateAndExecuteApp(int numberOfParameters)
{
string nameSpace = "Dynamic.Example";
string className = "HelloWorldApp";
string methodName = "GenerateMessage";
// 1. Generate the code
string script = BuildScript(nameSpace, className, methodName, numberOfParameters);
// 2. Compile the script
// 3. Load the Assembly
Assembly dynamicAssembly = CompileScript(script);
// 4. Execute the code
int[] arguments = Enumerable.Range(1, numberOfParameters).ToArray();
string message = ExecuteScript(dynamicAssembly, nameSpace, className, methodName, arguments);
Console.Out.WriteLine(message);
}
Generate the code
You say you already have item 1 sorted out, you can use StringBuilder, T4 templates or other mechanisms to generate the code files.
generating the code itself is its own question if you need help with that.
However, for our demo app, the following would work:
private static string BuildScript(string nameSpace, string className, string methodName, int numberOfParameters)
{
StringBuilder code = new StringBuilder();
code.AppendLine("using System;");
code.AppendLine("using System.Linq;");
code.AppendLine();
code.AppendLine($"namespace {nameSpace}");
code.AppendLine("{");
code.AppendLine($" public class {className}");
code.AppendLine(" {");
var parameterNames = Enumerable.Range(0, numberOfParameters).Select(x => $"p{x}").ToList();
code.Append($" public string {methodName}(");
code.Append(String.Join(",", parameterNames.Select(x => $"int {x}")));
code.AppendLine(")");
code.AppendLine(" {");
code.Append(" return $\"");
code.Append(String.Join(",", parameterNames.Select(x => $"{x}={{{x}}}")));
code.AppendLine("\";");
code.AppendLine(" }");
code.AppendLine(" }");
code.AppendLine("}");
return code.ToString();
}
For an input value of 3, the following code is generated:
using System;
using System.Linq;
namespace Dynamic.Example
{
public class HelloWorldApp
{
public string GenerateMessage(int p0,int p1,int p2)
{
return $"p0={p0},p1={p1},p2={p2}";
}
}
}
Compile the script (and Load it)
These are two discrete steps, however it is easiest to code them together in the same method, for this example we will ignore the generated dll and load the assembly directly into memory, that is generally the more likely use case for this type of scripting scenario anyway.
The hardest element of this is usually the referencing of the relevant dlls. There are a number of ways to achieve this, including loading all the dlls that are in the current executing context, I find a simple way to do this is to access the Assembly reference from the Type reference for the types we want to use inside the dynamic script:
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
Cut a long story short, this method compiles and loads the assembly into memory. It includes some crude compilation error handling, just to demonstrate how to do it:
private static Assembly CompileScript(string script)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(script);
// use "mytest.dll" if you want, random works well enough
string assemblyName = System.IO.Path.GetRandomFileName();
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
MetadataReference[] references = dlls.Distinct().Select(x => MetadataReference.CreateFromFile(x)).ToArray();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// Now we actually compile the script, this includes some very crude error handling, just to show you can
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
List<string> errors = new List<string>();
foreach (Diagnostic diagnostic in failures)
{
//errors.AddDistinct(String.Format("{0} : {1}", diagnostic.Id, diagnostic.Location, diagnostic.GetMessage()));
errors.Add(diagnostic.ToString());
}
throw new ApplicationException("Compilation Errors: " + String.Join(Environment.NewLine, errors));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
}
}
Execute the code
Finally, we can use reflection to instantiate an instance of the new app and then we can obtain a reference to the method and it. The name of the parameters is irrelevant, as long
we pass them through in the correct order:
for this demo the order is sort of irrelevant to, given they are all the same type ;)
private static string ExecuteScript(Assembly assembly, string nameSpace, string className, string methodName, int[] arguments)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
MethodInfo method = appType.GetMethod(methodName);
object result = method.Invoke(app, arguments.Cast<object>().ToArray());
return result as string;
}
Output
The final output from all this for our method with 3 passed into it is:
p0=1,p1=2,p2=3
So that was super crude, you can bypass most of the indirect reflection aspects through the use of Interfaces. If your generated script inherits from types or interfaces that the calling code also has a strong reference to, then ExecuteScript in the above example might look like this:
private static string ExecuteScript(Assembly assembly, string nameSpace, string className)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
if (app is KnownInterface known)
{
return known.GenerateMessage(1,2,3);
}
throw new NotSupportedException("Couldn't resolve known type");
}
The major benefit to using an interface or base class reference is that you can natively set properties or call other methods without having to reflect references to them all or to resort to using dynamic which would work, but becomes a bit harder to debug.
Of course the interface solution is hard to implement when we had a variable number of parameters, so that's not the best example, usually with dynamic scripts you would construct a known environment, say a known class and methods, but you might want to inject custom code into the body of the method.
It's a bit of fun in the end, but this simple example shows that C# can be used as a runtime scripting engine without too much trouble.

What is the F# equivalent of C# named arguments?

In the DotNetYaml sample code I'm looking at, there's a C# construct:
var deserializer = new Deserializer(namingConvention: new CamelCaseNamingConvention());
var order = deserializer.Deserialize<Order>(input);
What is the equivalent F# code? I've tried
let deserializer = new Deserializer(namingConvention=new CamelCaseNamingConvention())
deserializer.Deserialize<Meta>(input)
If you have a C# library that defines optional parameters, then you can use the syntax you are using in your question. To quickly show that's the case, I compiled the following C# code as a library:
using System;
namespace Demo {
public class MyClass {
public static void Foo(int first, string second = "foo", string third = "bar") { }
}
}
You can reference this and use it from F# as follows:
open Demo
MyClass.Foo(1, third="hi")
I tried to do this with YamlDotNet which, I guess, is the library that you were using, but I get an error that the Deserializer class does not have namingConvention as an argument, so my guess would be that you are probably using a different version of the library than you are thinking (or perhaps, my guess of what library you're using was wrong...).

Roslyn scripting with enforced script interface

I have simple IScript interface. And I want enforce that all scripts implement it.
public interface IScript<T>
{
T Execute(object[] args);
}
I want to use Roslyn scripting API to achive this. Something like this is possible with CSScript (see Interface Alignment).
var code = #"
using System;
using My.Namespace.With.IScript;
public class Script : IScript<string>
{
public string Execute()
{
return ""Hello from script!"";
}
}
";
var script = CSharpScript.Create(code, ScriptOptions.Default); // + Load all assemblies and references
script.WithInterface(typeof(IScript<string>)); // I need something like this, to enforce interface
script.Compile();
string result = script.Execute(); // and then execute script
Console.WriteLine(result); // print "Hello from script!"
Type safety is a static thing enforced a compile time (of your application). Creating and running a CSharpScript is done at runtime. So you cannot enforce type safety at runtime.
Maybe CSharpScript is not the right way to go. By using this SO answer,
You can compile a piece of C# code into memory and generate assembly bytes with Roslyn.
You would then change the line
object obj = Activator.CreateInstance(type);
to
IScript<string> obj = Activator.CreateInstance(type) as IScript<string>;
if (obj != null) {
obj.Execute(args);
}

Calling a function with named arguments in a hosted application

So I am hosting IronPython in my C# application. IronPhyton is used to implement a DSL for users. The DSL syntax should be something like this:
Ping(Message = "testOne1")
The hosting code looks like:
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
Action<string> ping = (message) => Console.WriteLine(message.ToString());
scope.SetVariable("Ping", ping);
var script = #"
Ping(Message = ""testOne1"")
";
engine.Execute(script, scope);
But this does not work because Action<string> does not keep name of the argument. Calling it without the parameter name works as expected:
Ping("testOne1")
How do I store a function and call it with named arguments?
To use named arguments you'll have to define the method statically. For example, I'll just put all DSL operations into an Operations static class.
public static class Operations {
public static void Ping(string Message) {
Console.WriteLine(Message);
}
}
Then named arguments will work:
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
// Load the assembly where the operations are defined.
engine.Runtime.LoadAssembly(Assembly.GetExecutingAssembly());
// Import the operations modules, settings their names as desired.
engine.Execute(#"
from Operations import Ping
", scope);
// Now named arguments will work...
var script = #"
Ping(Message = ""Ping!"")
";
engine.Execute(script, scope);
Now if I could give you some advise; I'd prefer to implement the actual Python API in Python, and have that call back into my .NET code as needed. For example, instead of having the "operations" defined in C#, you'd have an Operations.py file which defines your Python DSL:
# Get access to your .NET API
import clr
clr.AddReference("MyAPI")
import MyAPI
# Define the Ping call to call into your .NET API
def Ping(Message):
MyAPI.Ping(Message)
And your hosting code doesn't need to change at all.
Both are valid solutions, but the last one lets you iterate on your DSL easily.
Good luck!
The name of the parameter is defined by the name provided in the delegate type. In the case of Action<T>, the parameter name is obj.
public delegate void Action<in T>(
T obj
)
obj should work for you. Are you sure it isn't working? It works for me.
In an IronPython project I have a library:
namespace TestLibrary
{
public static class Test
{
public static readonly Action<string> WriteLine =
msg => Console.WriteLine(msg);
// the same works if I do this instead
//public static readonly Action<string> WriteLine = Console.WriteLine;
}
}
And this works:
from TestLibrary import Test
#Test.WriteLine(msg='foo') # error
Test.WriteLine(obj='foo') # works
Hosted, same deal:
var engine = Python.CreateEngine();
dynamic scope = engine.CreateScope();
Action<string> writeLine = msg => Console.WriteLine(msg);
// or even
//Action<string> writeLine = Console.WriteLine;
scope.writeLine = writeLine;
//engine.Execute("writeLine(msg='foo')", scope); // error
engine.Execute("writeLine(obj='foo')", scope); // works

Access host class from IronPython script

How do I access a C# class from IronPython script?
C#:
public class MyClass
{
}
public enum MyEnum
{
One, Two
}
var engine = Python.CreateEngine(options);
var scope = engine.CreateScope();
scope.SetVariable("t", new MyClass());
var src = engine.CreateScriptSourceFromFile(...);
src.Execute(scope);
IronPython script:
class_name = type(t).__name__ # MyClass
class_module = type(t).__module__ # __builtin__
# So this supposed to work ...
mc = MyClass() # ???
me = MyEnum.One # ???
# ... but it doesn't
UPDATE
I need to import classes defined in a hosting assembly.
You've set t to an instance of MyClass, but you're trying to use it as if it were the class itself.
You'll need to either import MyClass from within your IronPython script, or inject some sort of factory method (since classes aren't first-class objects in C#, you can't pass in MyClass directly). Alternatively, you could pass in typeof(MyClass) and use System.Activator.CreateInstance(theMyClassTypeObject) to new up an instance.
Since you also need to access MyEnum (note you're using it in your script without any reference to where it might come from), I suggest just using imports:
import clr
clr.AddReference('YourAssemblyName')
from YourAssemblyName.WhateverNamespace import MyClass, MyEnum
# Now these should work, since the objects have been properly imported
mc = MyClass()
me = MyEnum.One
You might have to play around with the script source type (I think File works best) and the script execution path to get the clr.AddReference() call to succeed.

Categories

Resources