I have a text file looks like:
AssembleComponent Motor = new AssembleComponent;
AssembleComponent Shaft = new AssembleComponent;
......
Motor.cost = 100;
Motor.quantity = 100;
Shaft.cost = 10;
Shaft.quantity = 100;
......
I wish to execute these code lines in C#, so that I will have these Motor.cost, Motor.quantity, Shaft.cost, Shaft.quantity variables stored in the memory for later calculation.
What can I do to achieve this?
Store it as XML instead
<?xml version="1.0" encoding="UTF-8"?>
<Components>
<Component name="Motor" cost="100" quantity="100" />
<Component name="Shaft" cost="10" quantity="100" />
</Components>
Assuming that you have this definition
public class AssembleComponent
{
public decimal Cost { get; set; }
public int Quantity { get; set; }
}
Load it like this
var components = new Dictionary<string, AssembleComponent>();
XDocument doc = XDocument.Load(@"C:\Users\Oli\Desktop\components.xml");
foreach (XElement el in doc.Root.Descendants()) {
string name = el.Attribute("name").Value;
decimal cost = Decimal.Parse(el.Attribute("cost").Value);
int quantity = Int32.Parse(el.Attribute("quantity").Value);
components.Add(name, new AssembleComponent{
Cost = cost, Quantity = quantity
});
}
You can then access the components like this
AssembleComponent motor = components["Motor"];
AssembleComponent shaft = components["Shaft"];
Note: Creating the variable names dynamically by calling the compiler at runtime is not very useful since you need to know them at compile-time (or design-time if you prefer) to do something useful with them. Therefore, I added the components to a dictionary. This is a good way of creating "variables" dynamically.
You can use Microsoft.CSharp.CSharpCodeProvider
to compile code on-the-fly.
Specifically, take a look at CompileAssemblyFromFile .
If it's just about data don't use a flat textfile but XML-instead.
You can deserialize the XML in to objects and perform the necessary actions on them.
Here's some code that I've used in the past, that does most of what you want though you may need to adapt it to your specific needs. In a nutshell, it does the following:
At that point it's like executing a normal static method, so when you say you want the results in memory for later use, you'd have to figure out how that would work.
public void CompileAndExecute(string CodeBody)
{
// Create the compile unit
CodeCompileUnit ccu = CreateCode(CodeBody);
// Compile the code
CompilerParameters comp_params = new CompilerParameters();
comp_params.GenerateExecutable = false;
comp_params.GenerateInMemory = true;
comp_params.TreatWarningsAsErrors = true;
comp_results = code_provider.CompileAssemblyFromDom(comp_params, ccu);
// CHECK COMPILATION RESULTS
if (!comp_results.Errors.HasErrors)
{
Type output_class_type = comp_results.CompiledAssembly.GetType("TestNamespace.TestClass");
if (output_class_type != null)
{
MethodInfo compiled_method = output_class_type.GetMethod("TestMethod", BindingFlags.Static | BindingFlags.Public);
if (compiled_method != null)
{
Delgate created_delegate = Delegate.CreateDelegate(typeof(System.Windows.Forms.MethodInvoker), compiled_method);
if (created_delegate != null)
{
// Run the code
created_delegate.DynamicInvoke();
}
}
}
}
else
{
foreach (CompilerError error in comp_results.Errors)
{
// report the error
}
}
}
public CodeCompileUnit CreateCode(string CodeBody)
{
CodeNamespace code_namespace = new CodeNamespace("TestNamespace");
// add the class to the namespace, add using statements
CodeTypeDeclaration code_class = new CodeTypeDeclaration("TestClass");
code_namespace.Types.Add(code_class);
code_namespace.Imports.Add(new CodeNamespaceImport("System"));
// set function details
CodeMemberMethod method = new CodeMemberMethod();
method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
method.ReturnType = new CodeTypeReference(typeof(void));
method.Name = "TestMethod";
// add the user typed code
method.Statements.Add(new CodeSnippetExpression(CodeBody));
// add the method to the class
code_class.Members.Add(method);
// create a CodeCompileUnit to pass to our compiler
CodeCompileUnit ccu = new CodeCompileUnit();
ccu.Namespaces.Add(code_namespace);
return ccu;
}
You have two main options:
This can be done following these steps: CodeGeneration
=> InMemory Compilation to Exe
==> Execution
.
You can design the construct similar to this:
public bool RunMain(string code)
{
const string CODE_NAMESPACE = "CompileOnFly";
const string CODE_CLASS = "Program";
const string CODE_METHOD = "Main";
try
{
var code_namespace = new CodeNamespace(CODE_NAMESPACE);
// add the class to the namespace, add using statements
var code_class = new CodeTypeDeclaration(CODE_CLASS);
code_namespace.Types.Add(code_class);
code_namespace.Imports.Add(new CodeNamespaceImport("System"));
// set function details
var method = new CodeMemberMethod();
method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
method.ReturnType = new CodeTypeReference(typeof(void));
method.Name = CODE_METHOD;
// add the user typed code
method.Statements.Add(new CodeSnippetExpression(code));
// add the method to the class
code_class.Members.Add(method);
// create a CodeCompileUnit to pass to our compiler
CodeCompileUnit code_compileUnit = new CodeCompileUnit();
code_compileUnit.Namespaces.Add(code_namespace);
var compilerParameters = new CompilerParameters();
compilerParameters.ReferencedAssemblies.Add("system.dll");
compilerParameters.GenerateExecutable = true;
compilerParameters.GenerateInMemory = true;
compilerParameters.TreatWarningsAsErrors = true;
var code_provider = CodeDomProvider.CreateProvider("CSharp");
var comp_results = code_provider.CompileAssemblyFromDom(compilerParameters, code_compileUnit);
if (comp_results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in comp_results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
//Get assembly, type and the Main method:
Assembly assembly = comp_results.CompiledAssembly;
Type program = assembly.GetType($"{CODE_NAMESPACE}.{CODE_CLASS}");
MethodInfo main = program.GetMethod(CODE_METHOD);
//runtit
main.Invoke(null, null);
return true;
}
catch(Exception compileException)
{
Console.Write(compileException.ToString());
return false;
}
}
In the code above we are actually creating a simple console Program.Main()
as
namespace CompileOnFly
{
internal class Program
{
static void Main()
{
//<your code here>
}
}
}
in memory then compiling it as executable in Memory and executing it. But the Main()
body //<your code here>
is added dynamically with the parameter code
to the method.
So If you have a script in the text file script.txt
as this:
Console.Write("Write your name: ");
var name = Console.ReadLine();
Console.WriteLine("Happy new year 2023 " + name);
You can simply read all the text and send it as parameter to it:
var code = File.ReadAllText(@"script.txt");
RunMain(code);
To run the statements in the script.txt
file.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.