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 생성 용도로 만들었던걸로 기억합니다.
속도는 기대하지 마십셔…