Created
June 9, 2025 11:37
-
-
Save PiotrFerenc/8de2933d24c0c5ac99c506ca7b37112d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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