from string to method - c#

Short question,
Is there a way in .NET 4.0 to take a string that represents the method body, and compile it into a Func/Action, or is there a library to do so?
Clarification:
I need something that will not generate any dll, it needs to be completely dynamic, something like eval() in javascript. I need to convert string into a Func/Action without creating dll.

You can use the CSharpCodeProvider class to compile source code into an assembly.
For example:
var compiler = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
var options = new CompilerParameters { OutputAssembly = path);
var results = compiler.CompileAssemblyFromFile(options, sourceFile);
To compile a single function, you can wrap it in a class with appropriate using statements to create a complete source file, then get a delegate using Reflection:
var assembly = results.CompiledAssembly;
var method = assembly.GetType("WrapperClassName").GetMethod("MethodName");
var delegate = (Action)Delegate.CreateDelegate(typeof(Action), method);
For a more complete example:
static readonly Assembly[] References = new[] { typeof(Enumerable).Assembly, typeof(Component).Assembly };
public Action CompileMethodstring source) {
var options = new CompilerParameters(References.Select(a => a.Location).ToArray()) {
GenerateInMemory = true
};
string fullSource = #"public static class HolderClass { public static void Execute() { \r\n" + source + "\r\n} }";
try {
var compiler = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
var results = compiler.CompileAssemblyFromSource(options, fullSource);
if (results.Errors.Count > 0)
throw new InvalidOperationException(String.Join(
Environment.NewLine,
results.Errors.Cast<CompilerError>().Select(ce => ce.ErrorText)
));
return (Action)Delegate.CreateDelegate(
typeof(Action),
results.CompiledAssembly.GetType("HolderClass").GetMethod("Execute")
);
} finally { options.TempFiles.Delete(); }
}

You could also use CS-Script.Net It is an embedded scripting platform that also you to do the following:
dynamic script = CSScript.LoadCode(#"using System;
public class Script
{
public void SayHello(string greeting)
{
Console.WriteLine(greeting);
}
}")
.CreateObject("*");
script.SayHello("Hello World!");
I've been using in production for almost 2 years now and it has been a great way to create configurable applications. I have a sample project if you are interested.

The CSharpCodeProvider might be what you are looking for. However, you'll need to create valid C# code (meaning, you'll need to create a class for that method).

Related

Fast way to find key-value pair in C#

Sounds pretty trivial but I really despair:
How do I import and match a value to a given key in an elegant fast way?
Just telephone area code - finding the matching prefix for a given zone (no multi-user). I have it as CSV but SQLite would be fine, too. SQLite connector? Or Dictionary? I don't know ...
Thank you in advance!
Nico
Simple as that. It's a console application.
Not pretty with the global creation and intialization but I didn't manage to do it across multiple source files by using only just a public class (type "Dictionary" and return this).
Main .cs file:
static class GlobalVar
{
public static Dictionary<string, string> areaCodesDict = new Dictionary<string, string>();
}
Second .cs file:
public class AreaCodes
{
public static void ParseCsv()
{
var path = ConfigurationManager.AppSettings["areaCodesCsv"];
using (var strReader = new StreamReader(path))
{
while (!strReader.EndOfStream)
{
var line = strReader.ReadLine();
if (line == null) { continue; }
var csv = line.Split(Convert.ToChar(ConfigurationManager.AppSettings["areaCodesCsvDelim"]));
// areaCodesDict.Add(key, value)
GlobalVar.areaCodesDict.Add(csv[0], csv[1]);
}
}
}
}
Example usage in Main.cs file again:
if (regexMatch.Success)
{
foreach (KeyValuePair<string, string> pair in GlobalVar.areaCodesDict)
{
if(destNumber.StartsWith(pair.Value))
{
destNumber = destNumber.Replace(pair.Value, pair.Key);
}
}
}

Add C# Script to the cache after compiling

Just looking for the solution where I want to add the c# script to the cache after compilation so that it can be accessed at any time when required.
Class which holds the cache details.
Public Class cCache
{
public string sSessionID{get;set;}
public string sName{get;set;}
public string sValue{get;set;}
}
C# script code used:
public static Run(string sName)
{
"Console.WriteLine(sName);"
}
When the button is clicked the method is called in the repository where it calls the another method with the script as the parameter to be complied and returns back the result in "MethodInfo" reflection. The script in the method info is invoked with parameter for the script and executed.
Code:
public string Button_CLick(string sScript)
{
MethodInfo methodInfo = WriteMethod(sScript);
cCache cChe= new cCache();
cChe.sSessionID="XYZ";
cChe.sName="Script";
cChe.sValue=MethodInfo.ToString();
if (methodInfo != null)
{
oResult = methodInfo.Invoke(null, new object[] { sName });
}
}
public MethodInfo WriteMethod(string sScript)
{
string sCode= #" using System;
namespace Scripting
{
public class AddScript
{
" + sScript + #"
}
}
";
CompilerParameters loParameters = new CompilerParameters();
loParameters.ReferencedAssemblies.Add("System.dll");
loParameters.GenerateInMemory = false;
ICodeCompiler provider = new CSharpCodeProvider().CreateCompiler();
CompilerResults results = provider.CompileAssemblyFromSource(loParameters, sCode);
if (results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
Type binaryFunction = results.CompiledAssembly.GetType("Scripting.AddScript");
return binaryFunction.GetMethod(Run);
}
The above code is working fine but before invoking the script method I want to add the complied script to the cache and then invoke or call it with parameter when ever required as the class property is string I am getting error when converting it to string, so please anyone can help me to sort this error.

Need help to upgrade IronPython 2.0.2 to 2.7.5, called from C#

I need to upgrade code calling IronPython from C# and would like to upgrade to IronPython 2.7.5. The problem is that one of the APIs has changed, and I am not familiar enough with the original code to fix it. I have written a console program that exhibits the problem
My Main:
class Program
{
static void Main()
{
var pythonTest = new PythonTest();
pythonTest.LoadScript();
Console.WriteLine("Area = {0}", pythonTest.Evaluate());
}
}
My test class:
public class PythonTest
{
private readonly ScriptEngine _engine;
private readonly ScriptScope _scope;
private ScriptSource _source;
private PythonFunction _currentFunction;
private readonly Dictionary<string, PythonFunction> _functions = new Dictionary<string, PythonFunction>();
private readonly double _scriptInput;
public PythonTest()
{
_scriptInput = 5;
_engine = Python.CreateEngine();
_scope = _engine.CreateScope();
}
public void LoadScript()
{
const string filename = #"../../Scripts/testscript.py";
_source = _engine.CreateScriptSourceFromFile(filename);
_source.Execute(_scope);
string firstFunction = "";
foreach (KeyValuePair<string, object> pair in _scope.GetItems())
{
var pairValue = pair.Value as PythonFunction;
if (pairValue != null)
{
_functions.Add(pair.Key, pairValue);
if (_functions.Count == 1)
{
firstFunction = _functions.Keys.First();
}
}
}
_currentFunction = _functions[firstFunction];
}
public string Evaluate()
{
if (_currentFunction == null)
return null;
var parameters = new ArrayList {_scriptInput};
LanguageContext cxt = Microsoft.Scripting.Hosting.Providers.HostingHelpers.GetLanguageContext(_engine);
var context = new CodeContext(new Scope(), cxt);
object result = _currentFunction.__call__(context, parameters.ToArray());
return result.ToString();
}
}
My test script:
from math import *
def AREA(h):
return (h * h)
This all works with the old Python DLLs. With the new DLLs the instantiation of the CodeContext (in the Evaluate method) is incorrect. The new API uses a PythonDictionary:
public CodeContext(PythonDictionary dict, ModuleContext moduleContext);
I don't know how to modify the code to fix this problem. Any help would be appreciated.
Your LanguageContext is a PythonContext so it can be cast. You can then use that along with a PythonDictionary to create a ModuleContext. Then you can use that along with a PythonDictionary to create your CodeContext:
PythonContext cxt = (PythonContext)Microsoft.Scripting.Hosting.Providers.HostingHelpers.GetLanguageContext(_engine);
PythonDictionary dict = new PythonDictionary();
ModuleContext modctx = new ModuleContext(dict, cxt);
var context = new CodeContext(dict, modctx);

How does a Tuple serialize to and deserialize from JSON?

I am curious about how the Tuple<T1, T2, T3, ...> serializes and deserializes. I searched using keywords "json" and "tuple" but I could not find what I want.
I test by UnitTest and Json.net, and the test codes is as following. The results shows Tuple<T1,T2,T3,...> is serializable and deserializable. So I can use them in my application.
Test codes
public class Foo {
public List<Tuple<string, string, bool>> Items { get; set; }
public Foo()
{
Items = new List<Tuple<string, string, bool>>();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (var a in Items)
{
sb.Append(a.Item1 + ", " + a.Item2 + ", " + a.Item3.ToString() + "\r\n");
}
return sb.ToString();
}
}
[TestClass]
public class NormalTests
{
[TestMethod]
public void TupleSerialization()
{
Foo tests = new Foo();
tests.Items.Add(Tuple.Create("one", "hehe", true));
tests.Items.Add(Tuple.Create("two", "hoho", false));
tests.Items.Add(Tuple.Create("three", "ohoh", true));
string json = JsonConvert.SerializeObject(tests);
Console.WriteLine(json);
var obj = JsonConvert.DeserializeObject<Foo>(json);
string objStr = obj.ToString();
Console.WriteLine(objStr);
}
}
Summary
Tuple.Create("own","hehe",true) serializes to {"Item1":"one","Item2":"hehe","Item3":true}
{"Item1":"one","Item2":"hehe","Item3":true} can be deserialized back to Tuple<string,string, bool>
Class Foo with Tuple data, can be serialized to json string, and the string can be deserialized back to Class Foo.
If you are looking for a short answer. I am using JsonConvert.
var testTuple = Tuple.Create(1234, "foo", true);
var serialized = JsonConvert.SerializeObject(testTuple);
Console.WriteLine(serialized);
// prints: {"Item1":1234,"Item2":"foo","Item3":true}
I made a minimal fiddle.
With .NET5 and soon .NET6 it's now recommended to use System.Text.Json over NewtonSoft. The important thing for this serializer with regard to tuples is to set the JsonSerializerOptions option IncludeFields, as otherwise tuple values are excluded by default.
Further, named tuples are just syntactic sugar which are replaced by standard Item1, Item2 notation by the compiler. To include names the simplest way is to use an anonymous object.
Below is a minimal example. (can paste into .NET fiddle with the .NET5 compiler)
using System;
using System.Collections.Generic;
using System.Text.Json;
public class Program
{
public static void Main()
{
JsonSerializerOptions options = new() { IncludeFields = true };
var testTuple = ("test" , "test1", 1324, false);
var serializedTuple = JsonSerializer.Serialize(testTuple, options);
Console.WriteLine(serializedTuple);
var testTuple2 = (NamedItem1: "test" , NamedItemTwo: "test1", TheIntegersName: 1324, ThisBoolHasAFirstNameIts: false);
var serializedTuple2 = JsonSerializer.Serialize(new {testTuple2.NamedItem1, testTuple2.NamedItemTwo, testTuple2.TheIntegersName, testTuple2.ThisBoolHasAFirstNameIts }, options);
Console.WriteLine(serializedTuple2);
}
}
output:
{"Item1":"test","Item2":"test1","Item3":1324,"Item4":false}
{"NamedItem1":"test","NamedItemTwo":"test1","TheIntegersName":1324,"ThisBoolHasAFirstNameIts":false}
Thank you Hinrich to the dotnetfiddle link above.
i used the same link, and got to know how Conversion works between Json objects and Tuples.
Below is the code :
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
var testTuple = Tuple.Create<int, string, bool>(1234, "foo", true);
var serialized = JsonConvert.SerializeObject(testTuple);
Console.WriteLine(serialized);
JObject test = ((JObject)JsonConvert.DeserializeObject(serialized));
string strSerialized = JsonConvert.SerializeObject(test);
//Tuple<int, string, bool> testTuple1 = JsonConvert.DeserializeObject<Tuple<int, string, bool>>(serialized); // WORKs
Tuple<int, string, bool> testTuple1 = JsonConvert.DeserializeObject<Tuple<int, string, bool>>(strSerialized); // WORKs
Console.WriteLine(testTuple1.Item1.ToString());
}
}
Hope someone finds this helpful.

Creating Instances of IronPython Classes From C#

I want to create an instance of an IronPython class from C#, but my current attempts all seem to have failed.
This is my current code:
ConstructorInfo[] ci = type.GetConstructors();
foreach (ConstructorInfo t in from t in ci
where t.GetParameters().Length == 1
select t)
{
PythonType pytype = DynamicHelpers.GetPythonTypeFromType(type);
object[] consparams = new object[1];
consparams[0] = pytype;
_objects[type] = t.Invoke(consparams);
pytype.__init__(_objects[type]);
break;
}
I am able to get the created instance of the object from calling t.Invoke(consparams), but the __init__ method doesn't seem to be called, and thus all the properties that I set from my Python script aren't used. Even with the explicit pytype.__init__ call, the constructed object still doesn't seem to be initialised.
Using ScriptEngine.Operations.CreateInstance doesn't seem to work, either.
I'm using .NET 4.0 with IronPython 2.6 for .NET 4.0.
EDIT: Small clarification on how I'm intending to do this:
In C#, I have a class as follows:
public static class Foo
{
public static object Instantiate(Type type)
{
// do the instantiation here
}
}
And in Python, the following code:
class MyClass(object):
def __init__(self):
print "this should be called"
Foo.Instantiate(MyClass)
The __init__ method never seems to be called.
This code works with IronPython 2.6.1
static void Main(string[] args)
{
const string script = #"
class A(object) :
def __init__(self) :
self.a = 100
class B(object) :
def __init__(self, a, v) :
self.a = a
self.v = v
def run(self) :
return self.a.a + self.v
";
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
engine.Execute(script, scope);
var typeA = scope.GetVariable("A");
var typeB = scope.GetVariable("B");
var a = engine.Operations.CreateInstance(typeA);
var b = engine.Operations.CreateInstance(typeB, a, 20);
Console.WriteLine(b.run()); // 120
}
EDITED according to clarified question
class Program
{
static void Main(string[] args)
{
var engine = Python.CreateEngine();
var scriptScope = engine.CreateScope();
var foo = new Foo(engine);
scriptScope.SetVariable("Foo", foo);
const string script = #"
class MyClass(object):
def __init__(self):
print ""this should be called""
Foo.Create(MyClass)
";
var v = engine.Execute(script, scriptScope);
}
}
public class Foo
{
private readonly ScriptEngine engine;
public Foo(ScriptEngine engine)
{
this.engine = engine;
}
public object Create(object t)
{
return engine.Operations.CreateInstance(t);
}
}
I think I solved my own question -- using the .NET Type class seems to have discarded Python type information.
Replacing it with IronPython.Runtime.Types.PythonType works quite well.
Looks like you're looking for the answer given to this SO question.

Categories