1 Kasım 2023

Rosyln Compiler - Memory Leak Free(Disposable)

Original Article: https://carljohansen.wordpress.com/2020/05/09/compiling-expression-trees-with-roslyn-without-memory-leaks-2/

I register handlebars helper for my codes that could be editable from text file.

Aim: Invoke public methods with TemplateHelper attribute.

Example Text File that contains C# Code:


using HandlebarsDotNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RosylnCompilerDemo
{
    public class DigitalInsertHandlebarsHelpers
    {
        [TemplateHelper]
        public void RegisterPriceWhole(IHandlebars _handlebars)
        {
            _handlebars.RegisterHelper("Price.Whole", (writer, context, arguments) =>
            {
                if (!arguments.Any())
                {
                    throw new HandlebarsException("Correct Format: {{#Price.Whole decimalPrice seperator}}, {{Price.Whole decimalPrice}}");
                }
                var price = arguments.At<string>(0);
                if (!string.IsNullOrWhiteSpace(price))
                {
                    string seperator = ",";
                    if (arguments.Length == 2)
                    {
                        seperator = arguments.At<string>(1);
                    }
                    price = price.Split(seperator).FirstOrDefault();
                }
                writer.WriteSafeString($"{price}");
            });
        }

        [TemplateHelper]
        public void RegisterPriceFraction(IHandlebars _handlebars)
        {
            _handlebars.RegisterHelper("Price.Fraction", (writer, context, arguments) =>
            {
                if (!arguments.Any())
                {
                    throw new HandlebarsException("Correct Format: {{#Price.Fraction decimalPrice seperator}}, {{Price.Whole decimalPrice}}");
                }
                var price = arguments.At<string>(0);
                if (!string.IsNullOrWhiteSpace(price))
                {
                    string seperator = ",";
                    if (arguments.Length == 2)
                    {
                        seperator = arguments.At<string>(1);
                    }
                    price = price.Split(seperator).LastOrDefault();
                }
                writer.WriteSafeString($"{price}");
            });
        }
    }
}





Code File:

using HandlebarsDotNet;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Reflection;
using System.Runtime.Loader;

namespace RosylnCompilerDemo
{
    public class TemplateHelperOperations
    {
        private class CollectibleAssemblyLoadContext : AssemblyLoadContext, IDisposable
        {
            public CollectibleAssemblyLoadContext() : base(true)
            { }

            protected override Assembly Load(AssemblyName assemblyName)
            {
                return null;
            }

            public void Dispose()
            {
                Unload();
            }
        }

        public static void RegisterHelpers(string code, IHandlebars handlebars)
        {
            var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);

            List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

            var references = assemblies.Where(q => !string.IsNullOrWhiteSpace(q.Location))
                .Select(q => MetadataReference.CreateFromFile(q.Location)).ToList();

            var syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
            var assemblyName = $"TempAssembly{Guid.NewGuid()}";
            var compilation = CSharpCompilation.Create(assemblyName)
                                .WithOptions(compilationOptions)
                                .AddReferences(references)
                                .AddSyntaxTrees(syntaxTree);


            using (var assemblyLoadContext = new CollectibleAssemblyLoadContext())
            using (var ms = new MemoryStream())
            {
                var emitResult = compilation.Emit(ms);
                if (emitResult.Success)
                {
                    ms.Seek(0, SeekOrigin.Begin);

                    // Load the compiled assembly
                    var assembly = Assembly.Load(ms.ToArray());

                    // Execute the library code
                    var helperMethods = assembly.GetTypes()
                        .Where(q => q.IsClass && q.IsPublic)
                        .Select(q => new { ClassName = q.FullName, Methods = q.GetMethods().Where(m => m.IsPublic && m.GetCustomAttributes(typeof(TemplateHelperAttribute), false).FirstOrDefault() != null) })
                        .Where(q => q?.Methods?.Any() ?? false)
                        .ToList();


                    foreach (var cls in helperMethods.Where(q => q.Methods?.Any() ?? false).GroupBy(q => q.ClassName))
                    {
                        if (!string.IsNullOrWhiteSpace(cls.Key))
                        {
                            var libClassType = assembly.GetType(cls.Key);
                            var libraryInstance = Activator.CreateInstance(libClassType);
                            foreach (var item in cls)
                            {
                                foreach (var method in item.Methods)
                                {
                                    method.Invoke(libraryInstance, new object[] { handlebars });
                                }
                            }
                        }
                    }
                }
                else
                {
                    foreach (var diagnostic in emitResult.Diagnostics)
                    {
                        Console.WriteLine(diagnostic.ToString());
                    }
                }
            }
        }
    }

}