如何在C#中使用括号表示法对字典进行URL编码
优化字典属性的URL查询字符串构建方案
问题背景
假设有如下类定义:
public class TestClass { public Dictionary<string, string> Property1 { get; set; } }
执行代码初始化对象:
var dictionary = new Dictionary<string, string>(); dictionary.Add("key1", "value1"); dictionary.Add("key2", "value2"); var newClass = new TestClass(); newClass.Property1 = dictionary;
需要将对象的字典属性编码成符合.NET Core API绑定要求的URL查询字符串,最终格式如下:
https://baseaddress.com/resource/?Property1[key1]=value1&Property1[key2]=value2
直接使用HttpUtility处理字典会得到其ToString()的默认结果,无法满足需求:
System.Collections.Generic.Dictionary`2[System.String,System.String]
已通过反射实现功能,但希望获得更高效的方案,现有代码如下:
var builder = new UriBuilder(uri); var query = HttpUtility.ParseQueryString(string.Empty); foreach (var propInfo in obj.GetType().GetProperties()) { var propName = propInfo.Name; var propValue = propInfo.GetValue(obj); if (propValue != null) { var dict = propValue as IDictionary; if (dict != null) { foreach (var key in dict.Keys) { var keyName = key; var keyValue = dict[key]; query.Add($"{propName}[{keyName}]", keyValue.ToString()); } } else { query.Add(propName, propValue.ToString()); } } } builder.Query = query.ToString(); return builder.Uri;
高效优化方案
方案1:针对已知类型硬编码(最优性能)
如果只需要处理固定类型(比如TestClass),直接跳过反射,硬编码属性访问是性能最高的方式,同时代码更简洁:
using Microsoft.AspNetCore.WebUtilities; var builder = new UriBuilder(uri); var queryParams = new List<KeyValuePair<string, string>>(); // 处理字典属性 if (newClass.Property1 != null) { foreach (var kvp in newClass.Property1) { queryParams.Add(new($"Property1[{kvp.Key}]", kvp.Value)); } } // 如需添加其他普通属性,直接追加 // queryParams.Add(new("OtherProperty", newClass.OtherProperty.ToString())); builder.Query = QueryHelpers.AddQueryString(string.Empty, queryParams); return builder.Uri;
这里使用QueryHelpers(来自Microsoft.AspNetCore.WebUtilities包)构建查询字符串,比HttpUtility.ParseQueryString更轻量,避免了NameValueCollection的额外开销。
方案2:带缓存的通用反射实现(平衡通用型与性能)
如果需要支持任意包含字典属性的类型,可通过缓存属性信息减少重复反射的开销,同时改用List<KeyValuePair<string, string>>配合QueryHelpers提升构建效率:
using Microsoft.AspNetCore.WebUtilities; using System.Collections; // 缓存类型的属性信息,避免重复反射 private static readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new(); public static Uri BuildUriWithDictionaryQuery(string uri, object obj) { var builder = new UriBuilder(uri); var queryParams = new List<KeyValuePair<string, string>>(); var objType = obj.GetType(); // 从缓存获取属性信息,没有则反射并缓存 if (!_propertyCache.TryGetValue(objType, out var propInfos)) { propInfos = objType.GetProperties(); _propertyCache[objType] = propInfos; } foreach (var propInfo in propInfos) { var propName = propInfo.Name; var propValue = propInfo.GetValue(obj); if (propValue == null) continue; // 处理字典类型属性 if (propValue is IDictionary dict) { foreach (DictionaryEntry entry in dict) { var keyStr = entry.Key?.ToString(); var valueStr = entry.Value?.ToString(); if (!string.IsNullOrEmpty(keyStr) && valueStr != null) { queryParams.Add(new($"{propName}[{keyStr}]", valueStr)); } } } // 处理普通属性 else { queryParams.Add(new(propName, propValue.ToString())); } } builder.Query = QueryHelpers.AddQueryString(string.Empty, queryParams); return builder.Uri; }
方案3:表达式树编译(极致性能的通用方案)
如果需要频繁处理大量不同类型对象,可使用表达式树编译属性访问委托,彻底消除反射调用的性能开销:
using Microsoft.AspNetCore.WebUtilities; using System.Collections; using System.Linq.Expressions; // 缓存编译后的属性访问委托 private static readonly Dictionary<Type, Func<object, IEnumerable<KeyValuePair<string, string>>>> _accessorCache = new(); public static Uri BuildUriWithDictionaryQuery(string uri, object obj) { var builder = new UriBuilder(uri); var objType = obj.GetType(); // 获取或编译属性访问委托 if (!_accessorCache.TryGetValue(objType, out var accessor)) { accessor = CompilePropertyAccessor(objType); _accessorCache[objType] = accessor; } var queryParams = accessor(obj).ToList(); builder.Query = QueryHelpers.AddQueryString(string.Empty, queryParams); return builder.Uri; } private static Func<object, IEnumerable<KeyValuePair<string, string>>> CompilePropertyAccessor(Type type) { var objParam = Expression.Parameter(typeof(object), "obj"); var castObj = Expression.Convert(objParam, type); var pairExpressions = new List<Expression>(); foreach (var propInfo in type.GetProperties()) { var propName = propInfo.Name; var propValueExpr = Expression.Property(castObj, propInfo); var nullCheck = Expression.NotEqual(propValueExpr, Expression.Constant(null)); if (typeof(IDictionary).IsAssignableFrom(propInfo.PropertyType)) { // 构建字典属性的遍历逻辑 var dictVar = Expression.Variable(typeof(IDictionary), "dict"); var enumeratorVar = Expression.Variable(typeof(IEnumerator), "enumerator"); var keyVar = Expression.Variable(typeof(object), "key"); var valueVar = Expression.Variable(typeof(object), "value"); var getEnumerator = Expression.Call(Expression.Property(dictVar, "Keys"), "GetEnumerator", Type.EmptyTypes); var moveNext = Expression.Call(enumeratorVar, "MoveNext", Type.EmptyTypes); var currentKey = Expression.Call(enumeratorVar, "get_Current", Type.EmptyTypes); var currentValue = Expression.Property(dictVar, "Item", keyVar); var pair = Expression.New( typeof(KeyValuePair<string, string>).GetConstructor(new[] { typeof(string), typeof(string) }), Expression.Call(typeof(string), "Format", null, Expression.Constant($"{propName}[{{0}}]"), keyVar), Expression.Call(valueVar, "ToString", Type.EmptyTypes) ); var loop = Expression.Loop( Expression.IfThenElse( moveNext, Expression.Block( Expression.Assign(keyVar, currentKey), Expression.Assign(valueVar, currentValue), Expression.Call(typeof(Enumerable), "Append", new[] { typeof(KeyValuePair<string, string>) }, Expression.Variable(typeof(IEnumerable<KeyValuePair<string, string>>), "pairs"), pair) ), Expression.Break(Expression.Label()) ) ); var dictBlock = Expression.Block( new[] { dictVar, enumeratorVar, keyVar, valueVar }, Expression.Assign(dictVar, propValueExpr), Expression.Assign(enumeratorVar, getEnumerator), loop ); pairExpressions.Add(Expression.Condition(nullCheck, dictBlock, Expression.Constant(Enumerable.Empty<KeyValuePair<string, string>>()))); } else { // 构建普通属性的处理逻辑 var pair = Expression.New( typeof(KeyValuePair<string, string>).GetConstructor(new[] { typeof(string), typeof(string) }), Expression.Constant(propName), Expression.Call(propValueExpr, "ToString", Type.EmptyTypes) ); pairExpressions.Add(Expression.Condition(nullCheck, Expression.Call(typeof(Enumerable), "Singleton", new[] { typeof(KeyValuePair<string, string>) }, pair), Expression.Constant(Enumerable.Empty<KeyValuePair<string, string>>()))); } } // 合并所有属性的查询参数 var combineExpr = pairExpressions.Aggregate((prev, next) => Expression.Call(typeof(Enumerable), "Concat", new[] { typeof(KeyValuePair<string, string>) }, prev, next)); var lambda = Expression.Lambda<Func<object, IEnumerable<KeyValuePair<string, string>>>>(combineExpr, objParam); return lambda.Compile(); }
该方案仅在第一次处理某类型时进行表达式编译,后续调用直接执行编译后的委托,性能接近硬编码,但实现复杂度较高,适合高性能需求的场景。
内容的提问来源于stack exchange,提问作者wamaral
相关产品推荐
相关产品推荐

