Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save PiotrFerenc/8de2933d24c0c5ac99c506ca7b37112d to your computer and use it in GitHub Desktop.
Save PiotrFerenc/8de2933d24c0c5ac99c506ca7b37112d to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Web;
public static class QueryStringGenerator
{
private static readonly ConcurrentDictionary<Type, Delegate> Cache = new();
public static string ToQuery<T>(this T obj)
{
if (obj == null) return "";
var generator = (Func<T, string>)Cache.GetOrAdd(typeof(T), _ => CreateGenerator<T>());
return generator(obj);
}
private static Func<T, string> CreateGenerator<T>()
{
var type = typeof(T);
var method = new DynamicMethod($"ToQuery_{type.Name}", typeof(string), new[] { type }, typeof(QueryStringGenerator).Module);
var il = method.GetILGenerator();
var sb = il.DeclareLocal(typeof(StringBuilder));
// StringBuilder sb = new StringBuilder();
il.Emit(OpCodes.Newobj, typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!);
il.Emit(OpCodes.Stloc, sb);
var appendMethod = typeof(StringBuilder).GetMethod("Append", new[] { typeof(string) })!;
var toStringMethod = typeof(object).GetMethod("ToString")!;
var urlEncodeMethod = typeof(HttpUtility).GetMethod("UrlEncode", new[] { typeof(string) })!;
var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanRead)
.ToArray();
for (int i = 0; i < props.Length; i++)
{
var prop = props[i];
// sb.Append("?key=") or "&key="
il.Emit(OpCodes.Ldloc, sb);
il.Emit(OpCodes.Ldstr, (i == 0 ? "?" : "&") + prop.Name.ToLower() + "=");
il.Emit(OpCodes.Callvirt, appendMethod);
il.Emit(OpCodes.Pop); // discard return value
// value = obj.Prop
il.Emit(OpCodes.Ldloc, sb); // sb
il.Emit(OpCodes.Ldarg_0); // obj
il.Emit(OpCodes.Callvirt, prop.GetMethod!); // get value
if (prop.PropertyType.IsValueType)
il.Emit(OpCodes.Box, prop.PropertyType);
// value.ToString()
il.Emit(OpCodes.Callvirt, toStringMethod);
// UrlEncode(value)
il.Emit(OpCodes.Call, urlEncodeMethod);
// sb.Append(encodedValue)
il.Emit(OpCodes.Callvirt, appendMethod);
il.Emit(OpCodes.Pop); // discard return value
}
// return sb.ToString()
il.Emit(OpCodes.Ldloc, sb);
il.Emit(OpCodes.Callvirt, typeof(StringBuilder).GetMethod("ToString", Type.EmptyTypes)!);
il.Emit(OpCodes.Ret);
return (Func<T, string>)method.CreateDelegate(typeof(Func<T, string>));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment