after reviewing all answers and question about compiling c# code programmatically, I choose this method:
CompileCSCAtRuntime
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using AA.UI.WPF.WND;
namespace AA.UI.WPF.COMMON
{
public static class CompileCSCAtRuntime
{
public static void HelloWorld()
{
string code = @"
using System;
using System.Windows;
using System.Windows.Forms;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var aassembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type CompileCSCAtRuntime = aassembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
Type Login = aassembly.GetType(""AA.UI.WPF.WND.Login"");
MethodInfo AccessLogin = CompileCSCAtRuntime.GetMethod(""AccessLogin"");
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");
}
}
}
";
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
string[] refr = {
"System",
"System.Windows",
"System.Windows.Forms",
"System.Drawing",
"Microsoft.CSharp",
"System.Core",
"System.Data"};
foreach (string r in refr)
parameters.ReferencedAssemblies.Add($"{r}.dll");
parameters.ReferencedAssemblies.Add($"Path_To_Assembly");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
MessageBox.Show(
results.Errors
.Cast<CompilerError>()
.Select(error => error.ErrorText)
.Aggregate((s, s1) => s += $";\r\n\r\n{s1}"));
return;
}
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("AA.UI.WPF.COMMON.Program");
MethodInfo main = program.GetMethod("Main");
main.Invoke(null, null);
}
public static Login AccessLogin()
{
return Login.Instance;
}
}
}
and Login
using System;
using AA.UI.WPF.COMMON;
namespace AA.UI.WPF.WND
{
public partial class Login
{
internal static Login Instance => _instance ?? (_instance = new Login());
private static Login _instance = null;
public Login()
{
InitializeComponent();
if (_instance == null) _instance = this;
}
internal void ShowMessage(string msg)
{
MessageBox.Show(msg);
}
}
update
it's work fine if I don't use reflection.
before the edit, I asked how can I can access a method outside of dynamically compiled c# code, I'm satisfied by @BionicCode answers. thank him. see comments.
your answer is completely true. since you post the first answer I have said you this is true. I use dynamic type as you said.
but now the last thing, I can't access the private or internal method, RuntimeBinderException: 'AA.UI.WPF.WND.Login.ShowMessage(string)' is inaccessible due to its protection level
. I think it's normal
goal
my goal is to inject some code as a string and run these like other normal code without reflection because it's too complicated and access to other classes, types, etc... inside current namespace FD.UI.WPF. if you know another easier way, please provide.
Like always, it would be very interesting to know the error message you get. I assume you are getting a compiler error?
But generally you should be able to execute methods from other assemblies.
Based on your code, you have to add the proper references to the CompilerParameters
, which is the assembly that contains the CompileCSCAtRuntime
type you wish to reference e.g., AA.UI.WPF.COMMON.dll
(I don't know the real assembly name at this point):
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Windows.dll");
parameters.ReferencedAssemblies.Add("AA.UI.WPF.COMMON.dll");
Now you can use the following code (note the added using
import):
string code = @"
using System;
using System.Windows;
using AA.UI.WPF.COMMON;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
CompileCSCAtRuntime.ShowMessage(""hello"");
}
}
}";
To add more flexibility, you have to dynamically load the assembly that contains the type and then execute the method using MethodInfo
(reflection). This way you don't need to add any assembly references or using
imports to the compiled code:
string code = @"
using System;
using System.Windows;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type type = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
MethodInfo method = type.GetMethod(""ShowMessage"");
method.Invoke(null, new[] { ""hello"" });
}
}
}";
You can use this snippet to output or log compiler errors:
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
MessageBox.Show(
results.Errors
.Cast<CompilerError>()
.Select(error => error.ErrorText)
.Aggregate((result, currentValue) => result += $";{Environment.NewLine}{currentValue}"));
}
Now, that the context has changed, you have to adopt your code to this new scenario. First of all dynamic
is not required here. You can use var
or object
instead, which has significantly less overhead (even when considering boxing scenarios). When using reflection, you should try to be as efficient as possible, because reflection itself is already expensive.
You are currently invoking Login.ShowMessage
using the instance that was returned by
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");
When using the instance, you are bound to the compiled access rules, which say that Login.ShowMessage
is internal
(you know that internal
restricts access to assembly scope).
Since your dynamically compiled code is compiled into a new dynamic assembly, the caller's scope (the dynamic code) does no longer satisfy this access constraint.
To get around this visibility constraints, you have to access and invoke non-public members using reflection. To get the MemberInfo
of any non-public member you always have to specify the appropriate BindingFlags
combinations:
string code = @"
using System;
using System.Windows;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type compileCSCAtRuntimeType = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
MethodInfo accessLoginMethod = compileCSCAtRuntimeType.GetMethod(""AccessLogin"");
object resultInstance = accessLoginMethod.Invoke(null, null);
Type resultType = resultInstance.GetType();
MethodInfo resultMethod = resultType.GetMethod(""ShowMessage"",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
resultMethod.Invoke(resultInstance, new [] { ""hi"" });
}
}
}";