You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 07:11:04