在 C# 中将匿名类型转换为键/值数组?

Posted

技术标签:

【中文标题】在 C# 中将匿名类型转换为键/值数组?【英文标题】:In c# convert anonymous type into key/value array? 【发布时间】:2011-03-29 18:10:15 【问题描述】:

我有以下匿名类型:

new data1 = "test1", data2 = "sam", data3 = "bob"

我需要一种方法来接收它,并在数组或字典中输出键值对。

我的目标是将其用作 HttpRequest 中的发布数据,因此我最终将连接到以下字符串中:

"data1=test1&data2=sam&data3=bob"

【问题讨论】:

【参考方案1】:

这只需一点点反思即可完成。

var a = new  data1 = "test1", data2 = "sam", data3 = "bob" ;
var type = a.GetType();
var props = type.GetProperties();
var pairs = props.Select(x => x.Name + "=" + x.GetValue(a, null)).ToArray();
var result = string.Join("&", pairs);

【讨论】:

var dict = props.ToDictionary(x => x.Name, x => x.GetValue(a_source, null)) 我们已经走到了这一步……我们可以把它做成一个班轮:var dict = a.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(a, null)); @kape123,确实如此。事实上,最新的 .NET 版本不再需要调用 ToArray(),这很好。无论如何,当前的响应非常适合 SO 中没有自动换行,所以我将保持原样。 @kape123 的答案非常有效,我找不到任何问题(到目前为止)。 kbrimington,你能(或者我应该)把这个添加到答案中吗? @Xan-KunClark-Davis,kape 的回答很好;但是,这确实是相同的答案。这就是为什么我赞成他的评论而不是将其整合到我的回复中。如今,在最新的 .NET Frameworks 上,我会将整个内容捆绑起来作为扩展方法。这将提高可重用性和清晰度。【参考方案2】:

如果您使用的是 .NET 3.5 SP1 或 .NET 4,您可以(ab)为此使用 RouteValueDictionary。它实现了IDictionary<string, object>,并有一个接受object并将属性转换为键值对的构造函数。

然后循环遍历键和值来构建查询字符串将是微不足道的。

【讨论】:

我说“滥用”是因为该类最初是为路由设计的(或者至少它的名称和命名空间暗示了这一点)。但是,它不包含特定于路由的功能,并且已用于其他功能(例如将匿名对象转换为 ASP.NET MVC htmlHelper 扩展方法中的 HTML 属性的字典。 我确实做到了这一点,但现在我需要从 RouteValueDictionary 回到异常对象,有什么想法吗?? 这甚至不是滥用它。微软做到了。例如,当您调用 HtmlHelper.TextBox... 时,您应该传递一个匿名类型来设置属性值。这实际上会导致 Razor 中的绑定错误(例如,尝试调用 @Html.Partial("~/Shared/_PartialControl.cshtml", new id="id",value="value"),它会引发绑定错误, 即使在局部视图中声明了@model dynamic。TextBox 方法内部调用“public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)”,它返回一个 RouteValueDictionary。所以你去。 @Triynko 这只是意味着微软也在滥用它。将来,如果他们可能会更改功能以执行特定于路由的操作。我建议您根据源代码制作自己的实现,并给它一个更合适的名称。 除非根据您的代码库,它会强制依赖 System.Web,否则可能不需要。有点希望这个类更通用,并位于更高的位置/在不同的命名空间中。【参考方案3】:

这是他们在 RouteValueDictionary 中的做法:

  private void AddValues(object values)
    
        if (values != null)
        
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
            
                object obj2 = descriptor.GetValue(values);
                this.Add(descriptor.Name, obj2);
            
        
    

完整来源在这里: http://pastebin.com/c1gQpBMG

【讨论】:

我尝试使用 pastebin 中的代码,Visual Studio 说一堆字典方法没有实现。我必须对 IDictionary 进行明确的强制转换。我刚刚将几个“this._dictionary”切换为“((IDictionary)this._dictionary)”【参考方案4】:

有一个将匿名对象转换为字典的内置方法:

HtmlHelper.AnonymousObjectToHtmlAttributes(yourObj)

它还返回RouteValueDictionary。请注意,它是静态的

【讨论】:

根据docs.microsoft.com/en-us/previous-versions/aspnet/…“在指定的 HTML 属性中用连字符 (-) 替换下划线字符 (_)”在某些情况下这可能是个问题。【参考方案5】:
using Newtonsoft.Json;
var data = new data1 = "test1", data2 = "sam", data3 = "bob";
var encodedData = new FormUrlEncodedContent(JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonConvert.SerializeObject(data))

【讨论】:

请在答案中添加一些解释,纯代码的答案会浪费审稿人的时间并且经常被误解,甚至会被删除。【参考方案6】:

为时已晚,但无论如何我会添加这个以获得更强大的解决方案。我在这里看到的那些有一些问题(比如他们不能与说 DateTime 一起工作)。因此,我建议先转换为 json (Newtonsoft Json.Net):

var data = new data1 = "test1", data2 = "sam", data3 = "bob";

var result = string.Join("&",
            JsonConvert.DeserializeObject<Dictionary<string, string>>(
            JsonConvert.SerializeObject(data))
            .Select(x => $"x.Key=x.Value")
        );

【讨论】:

【参考方案7】:

@kbrimington 的解决方案是一个很好的扩展方法——我的情况是返回一个 HtmlString

    public static System.Web.HtmlString ToHTMLAttributeString(this Object attributes)
    
        var props = attributes.GetType().GetProperties();
        var pairs = props.Select(x => string.Format(@"0=""1""",x.Name,x.GetValue(attributes, null))).ToArray();
        return new HtmlString(string.Join(" ", pairs));
    

我正在使用它将任意属性放入 Razor MVC 视图中。我从使用 RouteValueDictionary 的代码开始并在结果上循环,但这要整洁得多。

【讨论】:

这已经存在于盒子中(至少,它现在存在):HtmlHelper.AnonymousObjectToHtmlAttributes【参考方案8】:

我做了这样的事情:

public class ObjectDictionary : Dictionary<string, object>

    /// <summary>
    /// Construct.
    /// </summary>
    /// <param name="a_source">Source object.</param>
    public ObjectDictionary(object a_source)
        : base(ParseObject(a_source))
    

    

    /// <summary>
    /// Create a dictionary from the given object (<paramref name="a_source"/>).
    /// </summary>
    /// <param name="a_source">Source object.</param>
    /// <returns>Created dictionary.</returns>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="a_source"/> is null.</exception>
    private static IDictionary<String, Object> ParseObject(object a_source)
    
        #region Argument Validation

        if (a_source == null)
            throw new ArgumentNullException("a_source");

        #endregion

        var type = a_source.GetType();
        var props = type.GetProperties();

        return props.ToDictionary(x => x.Name, x => x.GetValue(a_source, null));
    

【讨论】:

【参考方案9】:

在@GWB 使用RouteValueDictionary 的建议的基础上,我编写了这个递归函数来支持嵌套匿名类型,并在这些嵌套参数前面加上其父键的前缀。

public static string EncodeHtmlRequestBody(object data, string parent = null) 
    var keyValuePairs = new List<string>();
    var dict = new RouteValueDictionary(data);

    foreach (var pair in dict) 
        string key = parent == null ? pair.Key : parent + "." + pair.Key;
        var type = pair.Value.GetType();
        if (type.IsPrimitive || type == typeof(decimal) || type == typeof(string)) 
            keyValuePairs.Add(key + "=" + Uri.EscapeDataString((string)pair.Value).Replace("%20", "+"));
         else 
            keyValuePairs.Add(EncodeHtmlRequestBody(pair.Value, key));
        
    

    return String.Join("&", keyValuePairs);

示例用法:

var data = new 
    apiOperation = "AUTHORIZE",
    order = new 
        id = "order123",
        amount = "101.00",
        currency = "AUD"
    ,
    transaction = new 
        id = "transaction123"
    ,
    sourceOfFunds = new 
        type = "CARD",
        provided = new 
            card = new 
                expiry = new 
                    month = "1",
                    year = "20"
                ,
                nameOnCard = "John Smith",
                number = "4444333322221111",
                securityCode = "123"
            
        
    
;

string encodedData = EncodeHtmlRequestBody(data);

encodedData 变为:

"apiOperation=AUTHORIZE&amp;order.id=order123&amp;order.amount=101.00&amp;order.currency=AUD&amp;transaction.id=transaction123&amp;sourceOfFunds.type=CARD&amp;sourceOfFunds.provided.card.expiry.month=1&amp;sourceOfFunds.provided.card.expiry.year=20&amp;sourceOfFunds.provided.card.nameOnCard=John+Smith&amp;sourceOfFunds.provided.card.number=4444333322221111&amp;sourceOfFunds.provided.card.securityCode=123"

希望这可以帮助处于类似情况的其他人。

编辑: 正如 DrewG 所指出的,这不支持数组。要正确实现对具有匿名类型的任意嵌套数组的支持将是非常重要的,并且由于我使用的 API 都没有接受数组(我不确定是否有一种标准化的方法可以使用表单编码对它们进行序列化),如果你们需要支持,我会留给你们。

【讨论】:

请注意,这不能正确处理数组。会堆栈溢出。需要这样的东西 if (type.IsArray) var arr = pair.Value as string[]; if (arr != null) foreach (var s in arr) keyValuePairs.Add((key + "[]=" + s).Replace(" ", "+"));

以上是关于在 C# 中将匿名类型转换为键/值数组?的主要内容,如果未能解决你的问题,请参考以下文章

转载 C#匿名函数 委托和Lambda表达式

30天C#基础巩固-----值类型/引用类型,泛型,空合并操作符(??),匿名方法

从转换为void返回委托的匿名函数返回

C#超级有用的一种类型—匿名类型

如何使用 Lambda 或 Linq 将匿名类型转换为原始类型成员

在 C# 中返回匿名类型