Страницы

Поиск по вопросам

понедельник, 25 ноября 2019 г.

Как программно откомпилировать класс и вызвать его метод?


Есть программа для обработки данных, и несколько раз в день меняется формула расчетов. Т.е. надо каждый раз вносить изменения в код определенного метода и компилировать сборку.
Вносить изменения в код метода можно программно, просто меняя фрагмент текста.
А как после замены формулы из своей программы откомпилировать код, вызвать мето
и получить результат?  
    


Ответы

Ответ 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

Зачем при записи чисел в конце добавляют .0?


Часто в коде программ числа записываются в виде цифры с .0 на конце. Например:

C: double s = 1.0 / 6.0;

Delphi: var d: Single; begin d := 16.0 + 1.0; end;

JS: var t = 90.0;

Возможны примеры и на других языках, но для начала предлагаю ограничиться этими тремя

Какой смысл несет добавление .0 в данных случаях?
В каких случаях .0 можно опускать, а в каких он меняет логику поведения программы?
    


Ответы

Ответ 1



Если говорить в общем, то добавление .0 к числу, изменяет его тип и превращает ег из целого, в дробное (число с плавающей точкой). Такое изменение типа числа влияет на то, как оно будет храниться в памяти и на то, как и какие операции с ним могут выполняться. В JS добавление .0 абсолютно лишено всякого смысла, т.к. там все числа изначально являются дробными. В Delphi так же не возникает необходимости вручную приводить целые числа к дробным т.к. компилятор замечательно справляется с этим самостоятельно, там где это нужно. Операци деления двух целых всегда возвращает дробное число и его невозможно по ошибке присвоить целому. Для особых случаев, когда надо выполнить целочисленное деление, есть специальный оператор div. А вот в Си и некоторых других языках, и для целочисленного, и для обычного делени используется один и тот же оператор деления / который ведёт себя по разному, в зависимости от типа операндов: если оба операнда целые, то и в результате деления будет целое (целочисленное деление) double i = 5 / 2; даст 2.0 (дробная часть результата операции отбрасывается, получается целое число, которое затем приводится к целевому типу double). если хоть один из операндов является дробным, то и результат будет дробным: double i = 5 / 2.0; даст 2.5. В Си, вместо .0 перед числом можно указывать тип числа с плавающей точкой (float/double) т.е. выполнять приведение типа: double i = 5 / (double) 2; такая конструкция используется, если операнд не число, а переменная целочисленного типа: int k = 2; double i = 5 / (double) k; // --> i = 2.5

Ответ 2



В конце литерала нужно добавлять .0 в том случае, если вам важно получить тип с плавающе точкой. Я приведу пример для С/С++, но он также будет актуальным для Java, C# и многих других языков: double X = 15 / 2; // Выведет 7 Здесь тип переменной - double, так что можно подумать, что в ответе получится 7.5 но это не так. Поскольку и 15 и 2 не имеют дробной части, они будут считаться целым числами при вычислении, и для них сначала будет произведено целочисленное деление (15 / 2 = 7), а потом полученный результат будет конвертирован и записан в переменную типа double. Это можно исправить, добавив пустую дробную часть к одному или к обоим литералам Если один из операндов является числом с плавающей точкой, то второй операнд тоже будет конвертирован в число с плавающей точкой, и вы получите правильный результат: double X = 15.0 / 2; // Выведет 7.5 Общее правило для C++ и подобных языков: добавляйте .0 к литералам тогда, когда ожидаете ответ типа float или double. Это поможет избежать неожиданностей.

Ответ 3



.0 задавать для значения переменной нужно для того, что бы явно показать как компилятор (интерпретатору) языка, так и программисту, что в контексте использования переменной речь идет о дробных числах. Один из возможных примеров тут. Изучайте базовые типы и приемы работы с переменными этих типов для тех платформ и языков, на которых программируете или собираетесь начать программировать. Если у вас есть вероятность, что в результатах ваших вычислений будут задействованы дробные числа - так и работайте с переменными соответствующих типов.

Ответ 4



В давние времена, когда появился Fortran - первый язык программирования высокого уровня, имеющий транслятор, это были азы, с которых начиналось обучение программированию. Добавление точки после числа сейчас называют неявным приведением типа. Когда надо было получить тип REAL от деления целых, писали Z=(X+0.)/(Y+0.) или Z=7./8. Это, конечно, было неисчерпаемым источником багов в программах. Тогда считалось несолидным добавлять ноль после точки. Это же целая лишняя дырка в перфокарте!

Зачем нужен upcast (повышающее приведение типа)?


Недавно обсуждалось, зачем нужен downcast — приведение типа от более общего к боле
конкретному. А нужен ли upcast (повышающее приведение) — явное приведение типов в обратную сторону, от более конкретного к более общему? Ведь мы ничего не теряем, работая с более конкретным объектом?
    


Ответы

Ответ 1



Для начала, общая причина, которая касается не только C#, но и большинства объектно-ориентированны языков: семантика. Если у программиста есть объект конкретного типа, он тем не менее может хотеть работать с ним как с более общим объектом: programming against an interface, not implementation! Это позволяет убедиться, что в коде не используются лишние, конкретные свойства что будет мешать в будущем обобщить код. Разумеется, обычно это слишком строгая цель, и без этого можно обойтись. Следующая причина — выбор перегрузки, неполиморфного метода. В зависимости от статическог типа объекта (при совпадающем динамическом типе) могут быть вызваны различные перегрузки при одинаково выглядящем коде. Примеры: Вызов нужной перегрузки: void f(object o) { Console.WriteLine("обрабатываем объект"); } void f(string s) { f((object)s); // избегаем рекурсии Console.WriteLine("дополнительная обработка для строки"); } string s = "Пушкин"; f(s); // вызывает перегрузку со строкой f((object)s); // вызывает перегрузку с объектом Ещё один пример, который часто встречается в коде: class X { public static bool operator == (X x1, X x2) { // оптимизация: проверим совпадение объектов if ((object)x1 == (object)x2) return true; // далее более дорогая проверка равенства по свойствам } } Вызов закрытого метода: class Base { public void X() { Console.WriteLine("нужный метод"); } } class Derived : Base { public new void X() { Console.WriteLine("бесполезный метод"); } } Derived d = new Derived(); ((Base)d).X(); Явная имплементация интерфейса не позволяет вызвать метод по имени. class X : IDisposable { void IDisposable.Dispose() {} } var x = new X(); // ... ((IDisposable)x).Dispose(); // по-другому не вызвать В случаях, когда тип переменной выводится неявно из типа другой переменной, бывают случаи, когда нас не устраивает автоматически выведенный тип. Пример: var list = new[] { 1, 2 }.ToList(); list.Add("ой"); Мы хотим получить список object'ов, но выведение типов даёт нам список int'ов. Мы можем написать var list = new[] { (object)1, 2 }.ToList(); list.Add("ой"); так всё будет компилироваться. Ещё один тесно связанный случай — тернарный оператор. Если типы альтернатив различны, компилятор не может найти общий тип выражения, и приходится помогать: Animal animal = nya ? new Cat() : new Dog(); // не компилируется Animal animal = nya ? (Animal)new Cat() : new Dog(); // компилируется (Очень похожая проблема возникает с Nullable-типами: int? result = good ? 1 : null требует явного преобразования одного из операндов-альтернатив.) Этот случай подсказал @Pavel Mayorov в комментариях, спасибо! Ещё одно применение — неявная упаковка (boxing). Например, функции типа GetEnumerator( могут вернуть объект типа-значения, который реализует интерфейс IEnumerator. Работать с ним не всегда удобно: static public IEnumerable MultiZip( this IEnumerable> sequences, Func, R> resultSelector) { var enumerators = sequences.Select(s => (IEnumerator)s.GetEnumerator()).ToList(); try { while (enumerators.All(en => en.MoveNext())) yield return resultSelector(enumerators.Select(en => en.Current)); } finally { foreach (var en in enumerators) en.Dispose(); } } Если бы мы забыли upcast к IEnumerator, то в enumerators мог бы оказаться набо value type (и это так и есть в нашем случае!). При этом, поскольку мы мутируем наши энумераторы (MoveNext), то для случая value type мы бы вызывали этот метод на копии значения, и таким образом код бы не сработал.

Что такое маршалинг?


Как бы вы объяснили "на пальцах", или с примером кода что это такое?
Вот сериализация - это ясно, это копирование структур из-в в простой форме, наприме
в форме байтового потока. И вот утверждается, что в этом случае сериализация по своей сути является маршалингом так как реализует "pass-by-value semantics". Что думаете?
    


Ответы

Ответ 1



Маршаллинг - это передача сущности из одного контекста в другой. Сериализация - это запись в виде последовательности элементов. Маршаллинг - это процесс более высокого уровня, чем сериализация. Обычно, если над передать структуру данных из одного процесса в другой - ее сериализуют, передают и десериализуют. Если параметр двусторонний - то и передавать его надо будет два раза, при этом это будет одна операция маршаллинга. Или возможна передача по ссылке, когда на другой стороне канала создается прокси-объект, а через канал передается не внутреннее состояние объекта, а вызовы его методов. Если речь идет о взаимодействии между управляемым и неуправляемым кодом - то маршаллин заключается в фиксировании адресов объектов или в копировании структур между управляемой и неуправляемой памятью, сериализация тут вовсе не используется.

Ответ 2



Насколько я понимаю, маршалинг и сериализация это почти синонимы, за исключение того, что при маршалинге записываются различные допольнительные данные которые говорят получателю объекта что сним делать и как - тоесть кодовая база. Можно сказать что Маршалинг это сериализация данных плюс кодовой базы объекта.

Анимация пешеходных и других маршрутов на растровой карте


Возникла идея, как сделать анимацию похода с семьей в ТЮЗ г. Санкт-Петербурга, допустим для жителей Ленинградской области. Чтобы реализовать анимацию нескольких маршрутов.

Маршрут движения будет на поезде до вокзала и  пешком собственно до театра. 

Это учебный пример, где будет использованs несколько техник реализации анимации: 


Анимация поезда,- составленного из символов юникода, который движется вдоль заданного пути, проложенного на растровой карте. 
Анимация рисования пешеходного маршрута от вокзала до ТЮЗ'а.
Анимация фигурок взрослых и их детей.
Анимация движения персонажей вдоль заданного маршрута.  


Ниже пример одной части анимации - прокладка пешеходного маршрута  




  
  
  
 
  
  
  






Как сделать, перечисленные выше, виды анимации?
    


Ответы

Ответ 1



Буду поэтапно, подробно объяснять все фрагменты анимации. Отдельные куски могут быт использованы для оживления раздела "Контакты" сайта заказчика. Опираясь на описание можно добавить собственные карты и проложить необходимы маршруты. Добавление растровой карты в SVG Для внедрения выбранного фрагмента растровой карты в SVG используется тег Прорисовка маршрутов движения поезда и людей Загружаем карту в векторный редактор и инструментом "Рисовать кривые Безье" добавляем узловые точки на маршруты движения. Сохраняем файл в формате *.svg и копируем из него патчи маршрутов Вычисляем длину маршрутов: для поезда это будет - 312px пешеходный маршрут - 409px Пишем команду анимации для пешеходного маршрута Анимация поезда,- составленного из символов юникода Юникод вагона - 🚃 Перемычки между вагонами - - Поезд будет идти по маршруту: Ниже код поезда без анимации: 🚂 - 🚃 - 🚃 - 🚃 - 🚃 - 🚃 - 🚂 Команда анимации движения поезда здесь будет несколько иная в отличии от анимации пешеходного маршрута, так как необходимо запустить по одному маршруту несколько вагонов. ` Ниже код анимации поезда: 🚃 - - 🚃 - 🚃 - 🚃 - 🚃 - 🚃 Анимация фигурок взрослых и их детей Анимация движения людей основана на анимации параметра "d" нескольких для фигур человека в нескольких фазах ходьбы, что при быстрой смене значений "d" создаёт иллюзию ходьбы. Ниже код анимации: Собираем все фрагменты анимации вместе Тайминг в SMIL SVG анимации просчитывается и реализуется довольно просто, в отличии от анимации CSS Если нужно запустить вторую анимацию строго после окончания первой анимации, то применяется следующая запись: begin="an1.end", где an1 - уникальный идентификатор первой анимации. Если вторая анимация должна запуститься через 1 сек. после окончания первой анимации, то - begin="an1.end+1s" Возможна и такая форма записи - begin="anTrain.end-7.5s",что означает запуск другой анимации за 7.5 сек. до окончания анимации с ID - anTrain Используя эти возможности можно очень гибко согласовывать взаимодействие отдельны фрагментов анимации и в дальнейшем, что-то изменить, подстроить, не составит большого труда. Ниже полный код всей анимации .container { width:100%; height:100%; } .play-audio { position:absolute; top:0%; left:50%; }
🚅 - 🚃 - 🚃 - 🚃 - 🚃 - 🚃 - 🚂
Приложение адаптивно и работает во всех современных браузерах, кроме IE Update 03.04.2019 г. Добавлен аудиоплейер. Использован трек: группы Машина времени - Разговор в поезде

& после объявления функции в классе


class A
{
public:
    void f() &;
};


Что означает & после объявления функции?
    


Ответы

Ответ 1



Это означает, что функция может быть вызвана для lvalue объекта класса A. Рассмотрите следующий пример #include class A { public: void f() & { std::cout << "A::f() &" << std::endl; } void f() && { std::cout << "A::f() &&" << std::endl; } }; int main() { A a; a.f(); A().f(); } Вывод на консоль этой программы A::f() & A::f() && В классе A функция f перегружена для lvale объектов класса A и для rvalue объектов класса A. В этой программе в предложении a.f(); вызывается функция f для lvalue объекта a. В этом же предложении A().f(); вызывается функция f для rvalue объекта A(). Другой пример. #include class A { public: void f() const & { std::cout << "A::f() const &" << std::endl; } void f() & { std::cout << "A::f() &" << std::endl; } }; void h( const A &a ) { a.f(); } int main() { A a; a.f(); h( a ); } Вывод на консоль A::f() & A::f() const & В этом примере класс A объявляет две перегруженные функции с именем f: одна для константных lvalue объектов, а другая для неконстантных lvalue объектов. В предложении a.f(); вызывается функция для неконстантных объектов, так как объект a не является константным. Однако внутри функции h h( a ); вызывается перегруженная функция для константных объектов, так как параметр функции является константой ссылкой. Еще один пример. Данная программа не будет компилироваться #include class A { public: void f() & { std::cout << "A::f() &" << std::endl; } }; int main() { A().f(); } Потому что rvalue не может быть привязана к неконстантной ссылке. Однако если объявить функцию как имеющую дело с константной ссылкой, то программа будет компилироваться #include class A { public: void f() const & { std::cout << "A::f() const &" << std::endl; } }; int main() { A().f(); } Чтобы было более понятно, то можно представить эти функции следующим образом. Каждая нестатическая функция-член класса имеет неявный параметр, который соответствует this Поэтому данные две функции-члены класса void f() const & и void f() & концептуально выглядят как void f( A * const & ) /*const &*/ и void f( A * & ) /*&*/ Поэтому последнюю функцию нельзя вызывать для временных объектов типа A *, тогд как первую - можно, так как константная ссылка может привязыватья к временному объекту. Вот пример, который упрощенно демонстрирует сказанное #include void f( int * const & ) { std::cout << "f( int * const & ) " << std::endl; } void f( int * & ) { std::cout << "f( int * & )" << std::endl; } int main() { int i = 10; int *pi = &i; f( pi ); f( &i ); } Вывод на консоль: f( int * & ) f( int * const & )

Анимация персонажа улитки


У меня есть, вот такая картинка симпатичного семейства улиток - 🐌 



Хочется сделать анимацию движения улиток, плюс к этому анимацию тела улитки, её усиков, глаз. 
  Понятно, что нужно искать или рисовать улитку в формате svg. В инете достаточно много файлов svg с улитками.
  Но мне понравилась именно эта картинка.  

Если есть svg файл большой улитки, то сделать уменьшенные копии - детёнышей не составит труда, используя команду scale(0.25):

  
`    


Анимация перемещения вдоль пути может быть реализована командой:


   
  


Как сделать остальную анимацию частей тела улиток?  
    


Ответы

Ответ 1



Анимация подошвы улитки Если есть необходимость подробно разобраться с техникой анимации атрибута "d" команды Path смотрите здесь Вся последующая реализация анимаций частей тела улитки основана именно на этой технике. Чтобы анимировать тело улитки необходимо найти патч, который рисует внешний контур подошвы улитки. Загружаем файл в векторный редактор и делаем копию этого патча. Сдвигаем контрольны узлы на копии патча, чтобы имитировать изменение контура подошвы улитки. Сохраняем фай и копируем эти патчи. Чтобы получилась анимация необходимо сделать плавный переход от первоначального патча ко второму и обратно. Для этого надо использовать анимацию атрибута патча “d” Анимация глаз и рта улитки Глаза улитки прикреплены к усикам, которые движутся, поэтому для более реалистичной картины необходимо обеспечить движение глаз влево-вправо и вверх-вниз. Для этого используется команда : Анимация рта улитки реализуется плавным изменением параметра “d” патча. Другими словам верхняя губа изгибается, имитируя открывание рта, а затем смыкается с нижней губой, как-бы, закрывая рот. Создание маленьких улиток Для создания детенышей улиток будем использовать команду для клонирования и команду scale(0.25) для уменьшения Анимация движения улиток Для анимации движения улиток используется команда движение по заданному пути Далее оформляем ландшафт, добавляем градиенты и музыку для создания настроения. Update 19.03.2019 г. Chrome теперь не позволяет автоматически запускать файлы *.mp3, поэтому пришлось добавить аудиоплайер. Firefox воспроизводит в автоматическом режиме файлы *.mp3 Добавлена анимация глаз улитки Добавлена анимация солнечных лучей .container { width:90%; height:90%; }
Использована песня: Bobby McFerrin don't worry be happy