简体   繁体   English

从 C# 中的文本文件执行代码行

[英]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.我希望在 C# 中执行这些代码行,以便将这些 Motor.cost、Motor.quantity、Shaft.cost、Shaft.quantity 变量存储在 memory 中以供以后计算。

What can I do to achieve this?我该怎么做才能实现这一目标?

Store it as XML instead 而是将其存储为XML

<?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. 您可以使用Microsoft.CSharp.CSharpCodeProvider即时编译代码。

Specifically, take a look at CompileAssemblyFromFile . 具体来说,看看CompileAssemblyFromFile

If it's just about data don't use a flat textfile but XML-instead. 如果只是数据,请不要使用纯文本文件,而应使用XML代替。

You can deserialize the XML in to objects and perform the necessary actions on them. 您可以将XML反序列化为对象并对其执行必要的操作。

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 展开文本,直到它变成有效的C#代码,然后编译并执行
  2. Parse it and execute it yourself (ie interpret it). 解析并自己执行(即解释)。

This can be done following these steps: CodeGeneration => InMemory Compilation to Exe ==> Execution .这可以按照以下步骤完成: 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在上面的代码中,我们实际上创建了一个简单的控制台Program.Main()作为

namespace CompileOnFly
{
    internal class Program
    {
       static void Main()
        {
            //<your code here>  
        }
    }
}

in memory then compiling it as executable in Memory and executing it.在 memory 中,然后在 Memory 中将其编译为可执行文件并执行。 But the Main() body //<your code here> is added dynamically with the parameter code to the method.但是Main()主体//<your code here>是通过参数code动态添加到方法中的。

So If you have a script in the text file script.txt as this:因此,如果您在文本文件script.txt中有一个脚本,如下所示:

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.运行script.txt文件中的语句。


声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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