Есть программа для обработки данных, и несколько раз в день меняется формула расчетов. Т.е. надо каждый раз вносить изменения в код определенного метода и компилировать сборку.
Вносить изменения в код метода можно программно, просто меняя фрагмент текста.
А как после замены формулы из своей программы откомпилировать код, вызвать мето
и получить результат?
Ответы
Ответ 1
[c:\temp\code.cs]
using System;
using System.Linq;
class Test {
static public int Run(params int[] values) {
return values.Sum(); // эта строка часто меняется
}
}
Этот код компилирует code.cs, вызывает метод Test.Run и выводит результат.
using System;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
var code = System.IO.File.ReadAllText(@"c:\test\code.cs");
var cps = new CompilerParameters() {
GenerateInMemory = true,
GenerateExecutable = false
};
cps.ReferencedAssemblies.AddRange(new[] {
"System.dll",
"System.Core.dll" });
var cp = new CSharpCodeProvider();
var cr = cp.CompileAssemblyFromSource(cps, code);
foreach (var err in cr.Errors)
Console.WriteLine(err);
if (!cr.Errors.HasErrors) {
var m = cr.CompiledAssembly.GetType("Test").GetMethod("Run");
var res = m.Invoke(null, new object[] { new[] { 1, 2, 3 } });
Console.WriteLine(res);
}
}
}
}
[Output]
6
Если значение var code указано прямо в Main, например:
var code = @"
using System;
// ...
";
то в code надо убрать \r\n
var cr = cp.CompileAssemblyFromSource(cps, Regex.Replace(code, "[\r\n]", " "));
иначе при компиляции будет ошибка: A namespace cannot directly contain members such as fields or methods.
Ответ 2
Добавлю, что при использовании сборок не из GAC нужно помещать скомпилированую сборку в каталог с используемыми DLL
static void ДобавитьссылкинаDLLВКаталоге(Type тип, System.Collections.Specialized.StringCollection ReferencedAssemblies)
{
string ИмяФайлаСборки = тип.Assembly.Location;
string Каталог = Path.GetDirectoryName(ИмяФайлаСборки);
string ИмяФайла = Path.GetFileName(ИмяФайлаСборки);
string[] files = Directory.GetFiles(Каталог, "*.dll");
// Console.WriteLine("Всего файлов {0}.", files.Length);
foreach (string f in files)
{
if (! String.Equals(f, ИмяФайлаСборки, System.StringComparison.CurrentCultureIgnoreCase))
if (ReferencedAssemblies.IndexOf(f) == -1)
ReferencedAssemblies.Add(f);
}
}
static CompilerResults СкомпилироватьОбертку(string строкаКласса,string ИмяКласса)
{
bool ЭтоСборкаГак = typeof(T).Assembly.GlobalAssemblyCache;
string Путь = Path.GetDirectoryName(typeof(T).Assembly.Location);
string OutputAssembly = Path.Combine(Путь, ИмяКласса) + ".dll";
var compiler = new CSharpCodeProvider();
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
parameters.ReferencedAssemblies.Add(typeof(AutoWrap).Assembly.Location);
if (!ЭтоСборкаГак)
{
parameters.ReferencedAssemblies.Add(typeof(T).Assembly.Location);
ДобавитьссылкинаDLLВКаталоге(typeof(T), parameters.ReferencedAssemblies);
}
else
{
string ИмяСборки = typeof(T).Assembly.ManifestModule.Name;
if (parameters.ReferencedAssemblies.IndexOf(ИмяСборки) == -1)
parameters.ReferencedAssemblies.Add(ИмяСборки);
}
if (ЭтоСборкаГак)
parameters.GenerateInMemory = true;
else
{ // parameters.GenerateInMemory = true;
parameters.GenerateInMemory = false;
parameters.OutputAssembly = OutputAssembly;
}
parameters.GenerateExecutable = false;
parameters.IncludeDebugInformation = true;
var res = compiler.CompileAssemblyFromSource(parameters, строкаКласса);
return res;
}
Кроме того можно использовать Scripting-API
String ПолучитьСтрокуДелегата()
{
string returnStr = @"return (MatchEvaluator)((match) =>
{
string x = match.Value;
// If the first char is lower case...
if (char.IsLower(x[0]))
{
// Capitalize it.
return char.ToUpper(x[0]) + x.Substring(1, x.Length - 1);
}
return x;
});";
return returnStr;
}
private void button_Click(object sender, RoutedEventArgs e)
{
// textBoxEval.AppendText(((D)СоздатьДелегат())().ToString());
string words = "надо заменить все первые буквы в словах на заглавные";
string pattern = @"\w+";
// MatchEvaluator evaluator = (MatchEvaluator)ПолучитьДелегат();
var scr = Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default
.WithReferences(typeof(MatchEvaluator).Assembly)
.WithImports("System", "System.Text.RegularExpressions");
var result = Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(ПолучитьСтрокуДелегата(), scr).Result;
MatchEvaluator evaluator = (MatchEvaluator)result;
textBoxEval.AppendText(Regex.Replace(words, pattern, evaluator));
}
Или более сложный вариант
string ПолучитьСтрокуКласса()
{
var res = @" class ClassTest
{
string строка;
public Func ЗаданнаяСтрока;
public ClassTest(string Строка)
{
строка = Строка;
ЗаданнаяСтрока = () => строка;
}
public static Func ПолучитьДелегатОбъекта(string Строка)
{
var obj = new ClassTest(Строка);
return obj.ЗаданнаяСтрока;
}
}
return new Func>(ClassTest.ПолучитьДелегатОбъекта);
";
return res;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
var scr = Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default
.WithImports("System");
var result = Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(ПолучитьСтрокуКласса(), scr).Result;
var делегат = (Func>)result;
textBoxEval.AppendText(делегат("Тестовая строка 2")() + Environment.NewLine); }
Кроме того можно использовать DynamicMethod
Для рефлектора есть дизассемблер в Reflection.Emit
Комментариев нет:
Отправить комментарий