1. Introduction
Ability to dynamically execute C #CodeIs a cool function. For example, we can enter a line of C # code in the console, and thenProgramAutomatically compile and execute this line of code to display the result to us. This is almost the simplest C # code interpreter.
Dynamic execution of C # code is a very useful function. For example, we can write some code in a file and load it when the Assembly is executed. You do not need to abort the program to change the code, when the program loads the code again, the new Code is automatically executed.
Next, I will write a simple C # code interpreter, and then add the dynamic interaction mechanism between the dynamic code and the interpreter environment to the C # code interpreter, to demonstrate a very powerful application.
2. Simple C # code interpreter
How to dynamically execute C # code is clearly described in jailu. Net's article "how to use C # To dynamically compile and execute code. Write a C # code interpreter as described in this article:
Using system;
Using system. Collections. Generic;
Using system. reflection;
Using system. Globalization;
Using Microsoft. CSHARP;
Using system. codedom;
Using system. codedom. compiler;
Using system. text;
Using system. IO;
Using system. xml;
Namespace Test
{
Class Program
{
Static void main (string [] ARGs)
{
Console. Write ("> ");
String cmd;
Context cxt = new context ();
While (cmd = console. Readline (). Trim ())! = "Exit ")
{
If (! String. isnullorempty (CMD ))
{
Console. writeline ();
Cxt. Invoke (CMD );
}
Console. Write ("\ n> ");
}
}
}
Public class context
{
Public csharpcodeprovider codeprovider {Get; set ;}
Public idictionary <string, assembly> assemblys {Get; set ;}
Public context ()
{
Codeprovider = new csharpcodeprovider (new dictionary <string, string> () {"compilerversion", "v3.5 "}});
Assemblys = new dictionary <string, assembly> ();
Assembly [] Al = appdomain. currentdomain. getassemblies ();
Foreach (assembly a in Al)
{
Addassembly ();
}
Appdomain. currentdomain. assemblyload + = new assemblyloadeventhandler (currentdomain_assemblyload );
}
Private void addassembly (Assembly)
{
If (! = NULL)
{
Assemblys. Add (A. fullname, );
}
}
Void currentdomain_assemblyload (Object sender, assemblyloadeventargs ARGs)
{
Assembly A = args. loadedassembly;
If (! Assemblys. containskey (A. fullname ))
{
Addassembly ();
}
}
Public compilerparameters createcompilerparameters ()
{
Compilerparameters CP = new compilerparameters ();
CP. generateexecutable = false;
CP. generateinmemory = true;
If (assemblys! = NULL)
{
Foreach (assembly a in assemblys. values)
{
CP. referencedassemblies. Add (A. Location );
}
}
Return CP;
}
Public void invoke (string cmd)
{
String inputaskstring = cmd. Trim ();
If (string. isnullorempty (input1_string) return;
String fullcmd = buildfullcmd (input1_string );
Compilerresults Cr = codeprovider. compileassemblyfromsource (createcompilerparameters (), fullcmd );
If (Cr. errors. haserrors)
{
Boolean recompileswitch = true;
Foreach (compilererror err in Cr. Errors)
{
// Cs0201: Only assignment, call, increment, decrement, and new object expressions can be
// Used as a statement
If (! Err. errornumber. Equals ("cs0201 "))
{
Recompileswitch = false;
Break;
}
}
// Re-compile
If (recompileswitch)
{
String dynaname = "temparg_dynamic _" + datetime. Now. ticks. tostring ();
Inputdomainstring = string. Format ("Var {0} =", dynaname) + inputdomainstring;
Inputpipeline string + = "; \ n system. Console. writeline (" + dynaname + ");";
Fullcmd = buildfullcmd (input1_string );
Cr = codeprovider. compileassemblyfromsource (createcompilerparameters (), fullcmd );
}
If (Cr. errors. haserrors)
{
Console. writeline ("Compilation error :");
Foreach (compilererror err in Cr. Errors)
{
Console. writeline (ERR. errornumber );
Console. writeline (ERR. errortext );
}
Return;
}
}
Assembly assem = Cr. compiledassembly;
Object dynamicobject = assem. createinstance ("test. dynamicclass ");
Type T = assem. GetType ("test. dynamicclass ");
Methodinfo minfo = T. getmethod ("methodinstance ");
Minfo. Invoke (dynamicobject, null );
}
Private string buildfullcmd (string input1_string)
{
String fullcmd = string. empty;
Fullcmd + = @"
Namespace Test
{
Public class dynamicclass
{
Public void methodinstance ()
{
"+ Input0000string + @";
}
}
}";
Return fullcmd;
}
}
}
After compilation and execution, a silly C # code parser can be used as a simple calculator:
3. Interaction between the interpreter and the interpreted code
If you store some variables in the interpreted code for future use, this interpreter is much more powerful. Assume that the names of these variables start with $, for example:
$ Myblogname = http://xiaotie.cnblogs.com"
It is defined in the interpreter environment (if the variable does not exist) or assigned to (if the variable already exists) a string variable named myblogname, pointing to the string "http://xiaotie.cnblogs.com ". However, system. Console. writeline ($ myblogname) retrieves and prints the string referenced by this variable.
To put it simply, let the interpreted code initialize and reference the variables in the interpreter.
How to implement it? This is the focus of this article.
First, define a sorteddictionary storage variable in the context class and provide index access:
Public sorteddictionary <string, Object> instances {Get; set ;}
Public object this [String InstanceName]
{
Get
{
If (instances. containskey (InstanceName ))
{
Return instances [InstanceName];
}
Else
{
Return NULL;
}
}
Set
{
If (instances. containskey (InstanceName ))
{
Instances. Remove (InstanceName );
}
Instances. Add (InstanceName, value );
}
}
The buildfullcmd method is changed:
Private string buildfullcmd (string input1_string)
{
String fullcmd = string. empty;
Fullcmd + = @"
Using test;
Public class dynamicclass
{
Private context m_context;
Public void methodinstance (context)
{
M_context = context;
"+ Input0000string + @";
}
}";
Return fullcmd;
}
In this way, context objects can be referenced in dynamically generated objects.
For external variables not defined in input0000string, replace $ argname with a randomly generated internal variable at the first time. At the end of the Code, store the internal variable in context.
Although the external variable $ argname can be referenced through (context [argname]. GetType () (context [argname]), the compiler reports an error when a value is referenced in this way. To solve this problem, a new class is required:
Public class objecthelper <t>
{
Private string m_objname;
Public context {Get; private set ;}
Public t OBJ
{
Get
{
Object OBJ = context [m_objname];
Return (t) OBJ;
}
Set {context [m_objname] = value ;}
}
Public objecthelper (context cxt, string objname)
{
M_objname = objname;
Context = cxt;
}
}
Replace $ argname, the external variable in input0000string, with (New objecthelper <m_context ["argname"]. getType ()> (m_context, "argname ")). OBJ can be used to reference defined external variables in dynamic code.
The Preprocessing code for input0000string is as follows:
RegEx re;
// Process uninitialized Environment Variables
Re = new RegEx (@ "^ (\ $) (\ W) + ");
If (input1_string! = NULL)
{
Match m = Re. Match (input1_string );
If (M! = NULL & M. length> 1)
{
String outargname = input1_string. substring (M. index, M. Length). substring (1 );
If (this [outargname] = NULL)
{
String innerargname = "temparg _" + outargname;
Input‑string = "Var" + input‑string. Replace ("$" + outargname, innerargname );
Inputdomainstring + = "; m_context [\" "+ outargname +" \ "] =" + innerargname + ";";
}
}
}
// Process other environment variables
Re = new RegEx (@ "(\ $) (\ W) + ");
Idictionary <string, string> argslist = new dictionary <string, string> ();
If (input1_string! = NULL)
{
Matchcollection MC = Re. Matches (input1_string );
If (MC! = NULL)
{
Foreach (Match m in MC)
{
If (M. length> 1)
{
String outargname = input1_string. substring (M. index, M. Length). substring (1 );
If (! Argslist. containskey (outargname ))
{
Object OBJ = This [outargname];
If (OBJ = NULL) throw new exception ("environment variable does not exist" + outargname );
String innerargname = string. format (@ "(New objecthelper <{0}> (m_context," "{1 }"")). OBJ ", obj. getType (), outargname );
Argslist. Add (outargname, innerargname );
}
}
}
}
Foreach (string outarg in argslist. Keys)
{
Input‑string = input‑string. Replace ("$" + outarg, argslist [outarg]);
}
}
This is simplified, that is, the format of the external variable must be $ argname = value, where $ argname must be at the beginning of the line.
As a result, for $ myblogname = "http://xiaotie.cnblogs.com". Because the myblogname variable does not exist, it is parsed:
VaR temparg_myblogname = "http://xiaotie.cnblogs.com ";
M_context ["myblogname"] = temparg_myblogname ;;
After definition, $ myblogname is resolved to (New objecthelper <system. String> (m_context, "myblogname"). OBJ;
Check the actual execution:
Complete code is downloaded here.
4. A very powerful application-break into the. Net program and check its execution status.
Orcshell is improved by using the above method (for details about orcshell, see the essay above: Implementing a simple csharpshell -- orcshell ). The new version of orcshell is downloaded here (. NET 3.5 is required ). Basically, it is an available small. NET Framework shell. You can dynamically view, create, and execute the. NET type. However, the automatic prompt and completion functions have not been completed yet, so it is inconvenient to use them.
The HELP command can view the list of commonly used commands:
LSC lists the types and subordinate namespaces in the current namespace. Format: LSC [name]
Dirc and LSC
CDC changes the current namespace. Format: CDC [. |... | Name]
My view all variables. Format: My. You can use $ argname to reference variables.
Alias to view all aliases. Format: alias
Use to add a namespace. Format: use [namespace]
Unuse removes the namespace. Format: unuse [namespace]
Import the Assembly in two ways: "import-f [fullpath]", "Import [partname]"