예전에 만들었던 간단한 템플릿 함수

using System;
using System.Reflection.Emit;
using System.Text;

namespace SimpleTemplate
{
    class Program
    {
        public record TempModel(string Name, string Day, int Date);

        static void Main(string[] args)
        {
            string template = "Hello, @(Name)! Today is @(Day) @(Date).\r\nHello, @@(Name)! Today is @@(Day) @@(Date).\r\n";
            {   // Generic
                var templateFunc = ParseTemplate<TempModel>(template);
                var model = new TempModel("Alice", "Wednesday", 31);
                Console.WriteLine(templateFunc(model));
            }
            {   // Object
                var templateFunc = ParseTemplate(template);
                var model = new { Name = "Bob", Day = "Friday", Date = 26 };
                Console.WriteLine(templateFunc(model));
            }
        }

        public static Func<T, string> ParseTemplate<T>(string template)
        {
            var method = new DynamicMethod("ProcessTemplate", typeof(string), new[] { typeof(T) });
            var il = method.GetILGenerator();

            var stringBuilderType = typeof(StringBuilder);
            var appendObjectMethod = stringBuilderType.GetMethod("Append", new[] { typeof(object) });
            var appendStringMethod = stringBuilderType.GetMethod("Append", new[] { typeof(string) });

            il.DeclareLocal(stringBuilderType);
            il.Emit(OpCodes.Newobj, stringBuilderType.GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Stloc_0);

            int lastIndex = 0;
            int startIndex = 0;
            while ((startIndex = template.IndexOf('@', startIndex)) != -1)
            {
                if (startIndex + 1 == template.Length)
                    break;
                if(template[startIndex + 1] != '(')
                {
                    startIndex += 2;
                    continue;
                }

                int endIndex = template.IndexOf(')', startIndex);
                if (endIndex == -1) break;

                string propertyName = template.Substring(startIndex + 2, endIndex - startIndex - 2);
                var property = typeof(T).GetProperty(propertyName);
                if(property == null)
                    throw new MissingMemberException(typeof(T).Name, propertyName);
                var propertyType = property.PropertyType;

                // Add text before the match
                if (startIndex > lastIndex)
                {
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldstr, template.Substring(lastIndex, startIndex - lastIndex).Replace("@@", "@"));
                    il.Emit(OpCodes.Callvirt, appendStringMethod);
                    il.Emit(OpCodes.Pop);
                }

                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Callvirt, property.GetGetMethod());
                if (propertyType.IsValueType)
                    il.Emit(OpCodes.Box, propertyType);
                il.Emit(OpCodes.Callvirt, appendObjectMethod); // Use Append directly
                il.Emit(OpCodes.Pop);

                startIndex = lastIndex = endIndex + 1;
            }

            // Add remaining text
            if (lastIndex < template.Length)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldstr, template.Substring(lastIndex).Replace("@@", "@"));
                il.Emit(OpCodes.Callvirt, appendStringMethod);
                il.Emit(OpCodes.Pop);
            }

            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Callvirt, stringBuilderType.GetMethod("ToString", Type.EmptyTypes));
            il.Emit(OpCodes.Ret);

            return method.CreateDelegate<Func<T, string>>();
        }

        public static Func<object, string> ParseTemplate(string template)
        {
            var method = new DynamicMethod("ProcessTemplate", typeof(string), new[] { typeof(object) });
            var il = method.GetILGenerator();

            var stringBuilderType = typeof(StringBuilder);
            var appendObjectMethod = stringBuilderType.GetMethod("Append", new[] { typeof(object) });
            var appendStringMethod = stringBuilderType.GetMethod("Append", new[] { typeof(string) });
            var getPropertyMethod = ((Func<object, string, object>)GetProperty).Method;

            il.DeclareLocal(stringBuilderType);
            il.Emit(OpCodes.Newobj, stringBuilderType.GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Stloc_0);

            int lastIndex = 0;
            int startIndex = 0;
            while ((startIndex = template.IndexOf('@', startIndex)) != -1)
            {
                if (startIndex + 1 == template.Length)
                    break;
                if(template[startIndex + 1] != '(')
                {
                    startIndex += 2;
                    continue;
                }

                int endIndex = template.IndexOf(')', startIndex);
                if (endIndex == -1) break;

                string propertyName = template.Substring(startIndex + 2, endIndex - startIndex - 2);

                // Add text before the match
                if (startIndex > lastIndex)
                {
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldstr, template.Substring(lastIndex, startIndex - lastIndex).Replace("@@", "@"));
                    il.Emit(OpCodes.Callvirt, appendStringMethod);
                    il.Emit(OpCodes.Pop);
                }

                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldstr, propertyName);
                il.Emit(OpCodes.Call, getPropertyMethod); // Use GetProperty Method below
                il.Emit(OpCodes.Callvirt, appendObjectMethod); // Use Append directly
                il.Emit(OpCodes.Pop);

                startIndex = lastIndex = endIndex + 1;
            }

            // Add remaining text
            if (lastIndex < template.Length)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldstr, template.Substring(lastIndex).Replace("@@", "@"));
                il.Emit(OpCodes.Callvirt, appendStringMethod);
                il.Emit(OpCodes.Pop);
            }

            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Callvirt, stringBuilderType.GetMethod("ToString", Type.EmptyTypes));
            il.Emit(OpCodes.Ret);

            return method.CreateDelegate<Func<object, string>>();

            static object GetProperty(object obj, string propertyName)
            {
                var property = obj.GetType().GetProperty(propertyName);
                if(property == null)
                    throw new MissingMemberException(obj.GetType().Name, propertyName);
                return property.GetValue(obj);
            }
        }
    }
}

예전에 아마 SQL 생성 용도로 만들었던걸로 기억합니다.
속도는 기대하지 마십셔…

3 Likes