I have following code snippet that i use to compile class at the run time.
//now compile the runner
var codeProvider = new CSharpCodeProvider(
new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
string[] references = new string[]
{
"System.dll", "System.Core.dll", "System.Core.dll"
};
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.AddRange(references);
parameters.OutputAssembly = "CGRunner";
parameters.GenerateInMemory = true;
parameters.TreatWarningsAsErrors = true;
CompilerResults result = codeProvider.CompileAssemblyFromSource(parameters, template);
Whenever I step through the code to debug the unit test, and I try to see what is the value of "result" I get an error that name "result" does not exist in current context. Why?
Are you debugging in release mode? This may happen to optimizations of unused variable.
For example:
public void OptimizedMethod()
{
int x = 5; // In optimized mode it's not possible to watch the variable
}
Code optimization happens when running in release mode, or when setting "Optimize code" in project properties (under build tab)
Related
Hey there im am running into a situation with the ServiceDescription importer.
In the code (which is pasted below) i am trying to import and compile a service reference from a WSDL file (stored locally) sadly due to customer implications i am not allowed to share this wsdl file.
However all the code runs trough well however the following line:
ServiceDescriptionImportWarnings warnings = importer.Import(nm, unit);
constantly keeps returning: ServiceDescriptionImportWarnings.NoCodeGenerated
could someone tell me if and where i am going wrong with this code ?
public string[] GenerateProxyAssembly(string pathOrURL)
{
Stream stream = File.OpenRead(pathOrURL);
ServiceDescription desc = ServiceDescription.Read(stream);
//find out the number of operations exposed by the web service
//store the name of the operations inside the string array
//iterating only through the first binding exposed as
//the rest of the bindings will have the same number
int i = 0;
Binding binding = desc.Bindings[0];
OperationBindingCollection opColl = binding.Operations;
string[] listOfOperations = new string[opColl.Count];
foreach (OperationBinding operation in opColl)
{
listOfOperations[i++] = operation.Name;
}
//initializing a ServiceDescriptionImporter object
ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
//set the protocol to SOAP 1.1
importer.ProtocolName = "Soap12";
//setting the Style to Client in order to generate client proxy code
importer.Style = ServiceDescriptionImportStyle.Client;
//adding the ServiceDescription to the Importer object
importer.AddServiceDescription(desc, null, null);
importer.CodeGenerationOptions = CodeGenerationOptions.GenerateNewAsync;
//Initialize the CODE DOM tree in which we will import the
//ServiceDescriptionImporter
CodeNamespace nm = new CodeNamespace("test");
CodeCompileUnit unit = new CodeCompileUnit();
unit.Namespaces.Add(nm);
//generating the client proxy code
ServiceDescriptionImportWarnings warnings = importer.Import(nm, unit);
if (warnings == 0)
{
//set the CodeDOMProvider to C# to generate the code in C#
System.IO.StringWriter sw = new System.IO.StringWriter();
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
provider.GenerateCodeFromCompileUnit(unit, sw, new CodeGeneratorOptions());
//creating TempFileCollection
//the path of the temp folder is hardcoded
TempFileCollection coll = new TempFileCollection(#"C:\wmpub\tempFiles");
coll.KeepFiles = false;
//setting the CompilerParameters for the temporary assembly
string[] refAssembly = { "System.dll", "System.Data.dll",
"System.Web.Services.dll", "System.Xml.dll" };
CompilerParameters param = new CompilerParameters(refAssembly);
param.GenerateInMemory = true;
param.TreatWarningsAsErrors = false;
param.OutputAssembly = "WebServiceReflector.dll";
param.TempFiles = coll;
//compile the generated code into an assembly
//CompilerResults results = provider.CompileAssemblyFromDom(param, unitArr);
CompilerResults results = provider.CompileAssemblyFromSource(param, sw.ToString());
this.assem = results.CompiledAssembly;
}
//return the list of operations exposed by the web service
return listOfOperations;
}
You don't share enough information, but i'll try to anwser.
Protocol could be the issue, i had similar problem, removing this line solved the problem
importer.ProtocolName = "Soap12";
So in my case, i was trying to connect to Soap 1.1 service with wrong protocol setting.
Soap 1.1 is default value.
Please check documentation:
https://learn.microsoft.com/en-us/dotnet/api/system.web.services.description.servicedescriptionimporter.protocolname?view=netframework-4.8
I am working on an application that should compile and debug C# code on the fly.
A simplified version of the code is included below.
What should be changed in this code to run the generated method step by step and get the state of the variables x and y after each step?
If everything should be changed that is okay, I am happy with any constructive response.
EDIT: to clarify: what I want to do is have my code debug the code that is generated with reflection, not the debug function in Visual Studio.
string code =
#"
namespace MyNameSpace
{
public class MyClass
{
public static int MyMethod()
{
var x = 3;
var y = 4;
return x * y;
}
}
}";
string namespaceName = "MyNameSpace";
string className = "MyClass";
string methodName = "MyMethod";
string language = "csharp";
string classFullname = namespaceName + "." + className;
CodeDomProvider provider = CodeDomProvider.CreateProvider(language);
CompilerParameters parameters = new CompilerParameters();
CompilerResults results;
parameters.OutputAssembly = "Compiler";
parameters.CompilerOptions = "/t:library";
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
parameters.IncludeDebugInformation = true;
results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count != 0)
{
throw new Exception("Code compilation errors occurred.");
}
var instance = results.CompiledAssembly.CreateInstance(classFullname, false);
// TODO run the method step by step and get the state after each step
This configuration may help you:
parameters.GenerateInMemory = false; //default
parameters.TempFiles = new
TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true);
parameters.IncludeDebugInformation = true;
parameters.TempFiles.KeepFiles = true
To debug the generated code you will need the pdb files. To have those while debugging your application, just have to tell the compiler where to save the temporary files. For this you can just add the following line to your parameters:
parameters.TempFiles = new TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true);
You can then step into the Invokation of your targeted method. The Code could look like this:
var method = instance?.GetType().GetMethod(methodName);
method?.Invoke(instance, BindingFlags.InvokeMethod, null, null, CultureInfo.CurrentCulture);
If you want the Debugger to automatically stop, when entering your "MyMethod", you can modify your string likes this:
string code =
#"using System.Diagnostics;
namespace MyNameSpace
{
public class MyClass
{
public int MyMethod()
{
Debugger.Break();
var x = 3;
var y = 4;
return x * y;
}
}
}";
Elchido pointed out in the comments that maybe I should look for an interpreter. After a bit of searching I came across CSI: A Simple C# Interpreter.
https://www.codeproject.com/Articles/10212/CSI-A-Simple-C-Interpreter
After investigating, my conclusion is that it is possible to use either and interpreter or the Codedom compiler to create debugger-like functionality, but it takes a significant effort.
The solution that I am working on involves splitting the code into separate statements and put all variables in an array.
The 'MyMethod' function is split into parts:
public static object Line1()
{
return 3;
}
public static object Line2()
{
return 4;
}
public static object Line3(object x, object y)
{
return x*y;
}
After compiling the code using Codedom compiler, I can do the following:
Dictionary<string, object> vars = new Dictionary<string, object>();
List<MethodInfo> lines = new List<MethodInfo>();
lines.Add(type.GetMethod("Line1"));
lines.Add(type.GetMethod("Line2"));
lines.Add(type.GetMethod("Line3"));
vars["x"] = lines[0].Invoke(instance, new object[] { });
vars["y"] = lines[1].Invoke(instance, new object[] { });
vars["#return"] = lines[2].Invoke(instance, new object[] { vars["x"], vars["y"] });
Note that this is not a working solution yet, a lot of work still has to be done to convert the 'MyMethod code' into separate lines and extract the variables. I will post an update when I have more/better code.
Click just left side of your code it will mark red dot that is called break point.After that when your code execute at the point it will break at the point and you can debug step by step bt pressing F10 key.
I can compile a DLL at runtime from a simple code string without any issues. Opening it in IL Spy shows exactly what it should. But if I put any extension methods in the code it outputs the following error:
error CS1644: Feature `extension methods' cannot be used because it is not part of the C# 2.0 language specification
The only help I could find says I should be able to set the "CompilerVersion" to "v3.5" by providing a dictionary of options to the code provider, but it doesn't help at all.
Here's my code:
var options = new System.Collections.Generic.Dictionary<string, string> { { "CompilerVersion", "v3.5" } };
var codeProvider = new Microsoft.CSharp.CSharpCodeProvider(options);
var provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp");
var parameters = new System.CodeDom.Compiler.CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "AutoGen.dll";
var results = provider.CompileAssemblyFromSource(parameters, code);
Debug.Log(results.Errors.DeepToString());
i created a dll contains a class named PersonVM like what you see below. and its working ...
public ActionResult Index()
{
using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
{
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "Per.dll";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, "public class PersonVM{ " + "public int id{get;set;}" +
"public string Name{get;set;}" + "public string LName{get;set;}" + " }");
}
Assembly assembly = Assembly.LoadFrom("Per.dll");
var type = assembly.GetType("PersonVM");
var d = type.GetProperties();
object obj = Activator.CreateInstance(type, true);
return View(obj);
}
but this code is working just one time in my index controller.
for example its not changing my dll class in here:
public ActionResult Conf()
{
using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
{
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "Per.dll";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, "public class PersonVM{ " + "public int id{get;set;}" +
"public string Name{get;set;}" + "public string LName{get;set;}" + "public string LNamee2 { get; set; }" + "public string L4 { get; set; }" + " }");
}
Assembly assembly = Assembly.LoadFrom("Per.dll");
var type = assembly.GetType("PersonVM");
object obj = Activator.CreateInstance(type, true);
List<ClassInfoVM> model = obj.GetType().GetProperties()
.Select(T => new ClassInfoVM()
{
PropName = T.Name,
TypeOfProp = T.PropertyType.Name
}).ToList();
return View(model);
}
there is no thing about any error.. it just doesn't changing my dll class...the dll class PersonVM is just contains the properties which i was set it, first time in Index
You can't load the same named DLL in a app domain twice using Assembly.LoadFrom.
See the Remarks section of the Assembly.LoadFrom function on the MSDN:
The LoadFrom method has the following disadvantages. Consider using
Load instead.
If an assembly with the same identity is already loaded, LoadFrom returns the loaded assembly even if a different path was specified.
One possible solution is let CSharpCodeProvider generate a random name for the assembly and load that, however if I where you I would seriously consider if you really need those classes to be built at runtime. Just build them at design time and give them two different names. Perhaps even make the version conf Conf dervive from the version in Index
I am going to ask a question that might sound weird.
Is there a way to build a new class during Runtime? Or at least, add a new property to an existing class.
I mean creating a class that doesn't exist and not an instance of an existing class. I could later on use reflections to load and use this class.
Adding a property to an existing type is not possible, but you can create a new type at runtime using Reflection.Emit. It's pretty complicated stuff, and it goes something like this:
AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(
assemblyName , AssemblyBuilderAccess.Run, assemblyAttributes);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ModuleName");
TypeBuilder typeBuilder = moduleBuilder.DefineType(
"MyNamespace.TypeName" , TypeAttributes.Public);
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
// Add a method
newMethod = typeBuilder.DefineMethod("MethodName" , MethodAttributes.Public);
ILGenerator ilGen = newMethod.GetILGenerator();
// Create IL code for the method
ilGen.Emit(...);
// ...
// Create the type itself
Type newType = typeBuilder.CreateType();
This code is just a sample. It could contain errors.
You can also generate classes by compiling C# source code at runtime using System.CodeDom, but I don't know a lot about that.
Take a look at the System.Reflection.Emit namespace. I've never used it myself but the classes in this namespace can be used to generate IL (intermediate language).
This is not a weird question - in some cases it might be very useful. For instance I use this technique for performance tests sometimes:
public static Type[] DynamicTypes;
public void CreateObjects()
{
var codeNamespace = new CodeNamespace( "DynamicClasses" );
codeNamespace.Imports.Add( new CodeNamespaceImport( "System" ) );
codeNamespace.Imports.Add( new CodeNamespaceImport( "System.ComponentModel" ) );
for( var i = 0; i < 2000; i++ )
{
var classToCreate = new CodeTypeDeclaration( "DynamicClass_" + i )
{
TypeAttributes = TypeAttributes.Public
};
var codeConstructor1 = new CodeConstructor
{
Attributes = MemberAttributes.Public
};
classToCreate.Members.Add( codeConstructor1 );
codeNamespace.Types.Add( classToCreate );
}
var codeCompileUnit = new CodeCompileUnit();
codeCompileUnit.Namespaces.Add( codeNamespace );
var compilerParameters = new CompilerParameters
{
GenerateInMemory = true,
IncludeDebugInformation = true,
TreatWarningsAsErrors = true,
WarningLevel = 4
};
compilerParameters.ReferencedAssemblies.Add( "System.dll" );
var compilerResults = new CSharpCodeProvider().CompileAssemblyFromDom( compilerParameters, codeCompileUnit );
if( compilerResults == null )
{
throw new InvalidOperationException( "ClassCompiler did not return results." );
}
if( compilerResults.Errors.HasErrors )
{
var errors = string.Empty;
foreach( CompilerError compilerError in compilerResults.Errors )
{
errors += compilerError.ErrorText + "\n";
}
Debug.Fail( errors );
throw new InvalidOperationException( "Errors while compiling the dynamic classes:\n" + errors );
}
var dynamicAssembly = compilerResults.CompiledAssembly;
DynamicTypes = dynamicAssembly.GetExportedTypes();
}
You might take a look at the System.CodeDom namespace. According to one of the pages linked from there:
The .NET Framework includes a mechanism called the Code Document Object Model (CodeDOM) that enables developers of programs that emit source code to generate source code in multiple programming languages at run time, based on a single model that represents the code to render.
I'm not at all an expert in this, I just remembered seeing it on my .NET Framework poster on my wall. :)
Edit: Since writing this answer, I have played with System.CodeDom a bit. I've written a blog post that uses some basic CodeDom that may help those wanting to get started with it.