我有一个Dictionary<string,string>,并想用这种模式将其压平:编写这样的扩展方法:

{key}={value}|{key}={value}|{key}={value}|


是否可以用LINQ解决这个问题?

评论

我建议在Exception中声明相关参数,如下所示:throw new ArgumentException(“ Parameter不能为空。”,“ source”)

@LoSauer很好,但更好的是抛出新的ArgumentException(“ Parameter不能为null。”,nameof(source));

#1 楼

这样的东西应该可以工作:

public static string ToString(this Dictionary<string,string> source, string keyValueSeparator, string sequenceSeparator)
{
  if (source == null)
    throw new ArgumentException("Parameter source can not be null.");

  var pairs = source.Select(x => string.Format("{0}{1}{2}", x.Key, keyValueSeparator, x.Value));

  return string.Join(sequenceSeparator, pairs);
}


评论


\ $ \ begingroup \ $
啊,.Select方法返回一个IEnumerable 。与好的ol'stringbuilder相比,这方面的性能有什么想法吗?谢谢,顺便说一句。
\ $ \ endgroup \ $
–马丁
2012年2月15日在11:37

\ $ \ begingroup \ $
String.format使用StringBuilder,但在这种情况下,它将为每个枚举创建一个新实例。 Trevor的解决方案当然更快,但是除非您的字典太大,否则我不认为您需要担心此处的性能:)
\ $ \ endgroup \ $
–马蒂亚斯
2012年2月15日在12:42

\ $ \ begingroup \ $
您不需要String.Format即可进行简单的字符串连接,而没有它,您的代码将更快。只需执行x.Key + keyValueSeparator + x.Value,编译器就会将其转换为String.Concat(),后者仅分配一个新字符串。
\ $ \ endgroup \ $
–克里斯·桑蒂(Chris Sainty)
2012年2月21日在21:24

\ $ \ begingroup \ $
@Mattias仅供参考,并不重要,但是您似乎在ToArray()调用之前在var pair行上缺少尾括号...
\ $ \ endgroup \ $
– dreza
2012年2月21日在23:15

\ $ \ begingroup \ $
@Mattias在发布到这里之前,您已经测试过代码了吗?它在.NET 4上给出了编译错误。
\ $ \ endgroup \ $
– Tomas
2012年8月30日12:23

#2 楼

实际上,您可以使用LINQ的Aggregate方法内联所有这些功能。

return d.Aggregate(new StringBuilder(), (sb, x) => sb.Append(x.Key + keySep + x.Value + pairSep), sb => sb.ToString(0, sb.Length - 1));


假设您可以阅读LINQ,它可能是最干净的。但这不是最快的。我尝试了所有提出的解决方案,而Mattias的答案实际上是迄今为止提出的最快的解决方案。

我有一个更快的解决方案。 br />多次调用Append的速度更快,并且像在我的Aggregate(更改为执行多个附加操作)中那样扩展运行它也更快。

当然,两者之间的性能差异所有这些都可以忽略不计。因此,选择一个最容易理解的代码,其他代码查看者会更好地理解它。

评论


\ $ \ begingroup \ $
假设pairSep只有一个字符长。要考虑不同的长度,应将其更改为返回sb.ToString(0,sb.Length-pairSep.Length);
\ $ \ endgroup \ $
– chrisofspades
14-10-17在18:56



#3 楼

Chris Sainty的答案可能是最快的,但这是最短的-根据要求使用Linq:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public static class DictionaryExtension 
{ 
    public static string ToStringFlattened(this Dictionary<string, string> source, string keyValueSeparator="=", char sequenceSeparator='|')
    {
        return source == null ? 
                           "" : source.Aggregate("", (str, v) => 
                                         str + v.Key 
                                             +  keyValueSeparator 
                                             + v.Value 
                                             + sequenceSeparator)
                                      .TrimEnd(sequenceSeparator);
    }
}

static void Main()
{
    Console.WriteLine(
      new Dictionary<string, string>() 
        { { "key1", "val1" }, { "key2", "value2" }, }.ToStringFlattened()
    );
}


在.Net Framework> = 4.0中,您可以仅使用Zip:

public static string ToStringFlattened(this Dictionary<string, string> source, string keyValueSeparator="=", string sequenceSeparator="|")
{
    return source == null ? "" : string.Join(sequenceSeparator, source.Keys.Zip(source.Values, (k, v) => k + keyValueSeparator + v));
}


注意:如果要在最后保留sequenceSeparator,请从第一个方法中删除Trim,然后将sequenceSeparator添加('+')到第二个例子。

评论


\ $ \ begingroup \ $
很好,但是str + = v.Key应该是str + v.Key,因为我们没有修改str,我们只是在为其传播一个新值
\ $ \ endgroup \ $
– Aluan Haddad
16-09-22在5:18

\ $ \ begingroup \ $
感谢Aluan!原则上你是对的。实际上,在这种情况下,几乎没有什么区别:在您的情况下,生成的已编译IL代码将是传递给string.Concat(string,string,string,string,string)的四重字符串声明,在所述情况下是四重字符串数组,随后调用string.Concat(string [])。
\ $ \ endgroup \ $
–洛伦茨·罗索尔(Lorenz Lo Sauer)
16-9-29在5:12

\ $ \ begingroup \ $
的确如此,因为突变是无关紧要的。我没有编辑的声誉,但是您在示例中使用=而不是加号引入了一个错误。
\ $ \ endgroup \ $
– Aluan Haddad
16-9-29在6:30

\ $ \ begingroup \ $
当然。谢谢。对于错字,它也应显示出令人关注的速度差异。否则,如所示,没有任何内容。
\ $ \ endgroup \ $
–洛伦茨·罗索尔(Lorenz Lo Sauer)
16-09-29在7:01

#4 楼

您是否特别需要将其作为字符串使用,还是只想将字典作为设置存储在项目属性的“应用程序设置”部分中?如果仅需要将字典存储在设置文件中,则以下代码也可以满足您的需要:

(无论是什么),将其序列化,函数将返回表示该对象的字符串,如下所示:字符串到Serialize,它将返回一个对象。您将需要使用原始类型,例如:

public static string Serialize(object obj)
    {
        MemoryStream memorystream = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(memorystream, obj);
        byte[] mStream = memorystream.ToArray();
        string slist = Convert.ToBase64String(mStream);
        return slist;
    }

public static object Unserialize(string str)
    {
        byte[] mData = Convert.FromBase64String(str);
        MemoryStream memorystream = new MemoryStream(mData);
        BinaryFormatter bf = new BinaryFormatter();
        Object obj = bf.Deserialize(memorystream);
        return obj;
    }


#5 楼



首先,如果尚未创建ForEach扩展方法,请添加一个:

public static class EnumerableExtensions
{
    public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
    {
        foreach (var item in items)
        {
            action(item);
        }
    }
}


public static class DictionaryExtensions
{
    public static string ToString<TKey, TValue>(
        this IDictionary<TKey, TValue> dictionary, string keyValueSeparator, string sequenceSeparator)
    {
        var stringBuilder = new StringBuilder();
        dictionary.ForEach(
            x => stringBuilder.AppendFormat("{0}{1}{2}{3}", x.Key.ToString(), keyValueSeparator, x.Value.ToString(), sequenceSeparator));

        return stringBuilder.ToString(0, stringBuilder.Length - sequenceSeparator.Length);
    }
}


然后调用它:

var dictionary = new Dictionary<string, string>();
dictionary.Add("key1", "value1");
dictionary.Add("key2", "value2");
dictionary.Add("key3", "value3");

System.Console.WriteLine(dictionary.ToString("=", "|"));


评论


\ $ \ begingroup \ $
顺便说一下,List 类已经有了ForEach。 :)所以无论什么ToTo()。ForEach已经在BCL中实现。
\ $ \ endgroup \ $
– Lars-Erik
2012年2月15日14:10

\ $ \ begingroup \ $
@ Lars-Erik-在IEnumerable 上使用扩展方法的原因是避免仅为了获得对ForEach方法的访问权限而创建和填充列表实例的原因。
\ $ \ endgroup \ $
–特雷弗·皮利(Trevor Pilley)
2012-2-15 14:22

\ $ \ begingroup \ $
@TrevorPilley如果性能确实是一个问题,我同意。如果是这样,我认为您还必须考虑一些其他事项-它何时执行,如果您伪装了IQueryable等,该怎么办。
\ $ \ endgroup \ $
– Lars-Erik
2012-2-15 14:44

#6 楼

也可以使用StringBuilder进行纯LINQ:

    public static string ToStringLinq<TKey, TValue>(this Dictionary<TKey, TValue> source, string keyValueSeparator, string sequenceSeparator)
    {
        return source.Aggregate(new StringBuilder(),
            (acc, pair) => acc.AppendFormat("{0}{1}{2}{3}", pair.Key, keyValueSeparator, pair.Value, sequenceSeparator),
            builder => builder.Length > sequenceSeparator.Length ?
                builder.ToString(0, builder.Length - sequenceSeparator.Length)
                : String.Empty
            );
    }


#7 楼

纯粹的LINQ方式是通过Aggregate扩展方法实现的:字符串。

或者:

public static string ToStringLinq<TKey, TValue> (this Dictionary<TKey, TValue> source, string keyValueSeparator, string sequenceSeparator)
{
    return source.Aggregate(string.Empty, (acc, pair) => string.Format("{0}{1}{2}{3}{4}", acc, sequenceSeparator, pair.Key, keyValueSeparator, pair.Value));
}


这会将分隔符放在结尾而不是开头。

如果没有前导或尾随的分隔符很重要,则可以肯定地更新lambda以解决累加器为空的情况,或者可以将代码添加到种子中,并为第一个元素添加字符串,然后在其余部分进行聚合字典,如下所示:

public static string ToStringLinq<TKey, TValue> (this Dictionary<TKey, TValue> source, string keyValueSeparator, string sequenceSeparator)
{
    return source.Aggregate(string.Empty, (acc, pair) => string.Format("{0}{1}{2}{3}{4}", acc, pair.Key, keyValueSeparator, pair.Value, sequenceSeparator));
}


当然,这也遇到了Mattias回答的相同问题,由于重复string.Format,它会做更多的对象创建调用,但是对于性能不是问题的情况,单个聚合函数调用可以是解决问题的非常紧凑的方法。

#8 楼

来自Mattias解决方案:



string.Concat()快于string.Format()

无需在string.Join()之前构建阵列


public static string ToString(this Dictionary<string,string> source, string keyValueSeparator, string sequenceSeparator)
{
    if (source == null) throw new ArgumentException("Parameter source can not be null.");

    var pairs = source.Select(x => string.Concat(x.Key, keyValueSeparator, x.Value));

    return string.Join(sequenceSeparator, pairs);
}