简体   繁体   中英

Execute code lines from a text file in C#

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:

  • Create a temporary namespace and a public static method in that namespace.
  • Compile the code to an in-memory assembly.
  • Extract the compiled method and turn it into a delegate.
  • Execute the delegate.

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:

  1. Expand the text until it becomes valid C# code, compile it and execute it
  2. Parse it and execute it yourself (ie interpret it).

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM