【问题标题】:Using a condition within a collection initializer在集合初始化器中使用条件
【发布时间】:2021-08-16 13:40:44
【问题描述】:

我想将一些值放入字典中:

// Pretend these values are retrieved from a database or something
string firstName = "John";
string lastName = "Smith";

List<string> listHobbies = new List<string> () {"hockey","soccer"};

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", listHobbies}
};

但是,在某些情况下,List&lt;string&gt; 变量可能是一个空列表。如果是这种情况,我不想将 {"hobbies", listHobbies} 键添加到结果字典中。

有没有办法在字典的集合初始化器中实现这个检查?还是我使用 if 语句检查列表的大小并在声明后立即删除键?

编辑: 本质上,我想知道 C# 是否允许这样的事情:

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", listHobbies} unless (listHobbies.Count == 0) // don't add the "hobbies" key if the list is empty.
};

我目前可以通过执行以下操作来实现:

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", listHobbies}
};
}.Concat(listHobbies != 0 ? new Dictionary<string, dynamic>() { { "hobbies", listHobbies } } : new Dictionary<string, dynamic>()).ToDictionary(k => k.Key, v => v.Value);

但这太丑了。

编辑 2: 我找到了一个可以接受的解决方案,如果其他人可以提供更好看的替代方案,请随时提出建议:

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName},
    {"hobbies", !listHobbies.Any() ? listHobbies : null}
}
.Where(entry => entry.Value != null)
.ToDictionary(k => k.Key, v => v.Value);

【问题讨论】:

  • 检查 if (listHobbies.Count() > 0) 怎么样
  • 是的,我可以在 Dictionary 声明之前或之后检查该条件并相应地删除(或只是不添加)密钥,但是我正在寻找一种在字典初始化中执行此操作的方法干净的代码。
  • 我觉得在集合初始化器中放置条件可能是不好的做法。当我们确定值时,我们使用集合初始化器,在其他情况下,运行带条件的循环可能更可取。 Where(x=>x!=null) 将导致整个集合再次迭代以进行空检查。
  • 我的感觉是你不需要字典。您需要一个具有 3 个属性的对象:firstName、lastName 和 hobbies。然后,您可以将此对象填充到 Dictionary 或其他集合中。
  • 您在 Edit2 中提出的解决方案是一个很好的解决方案。它清楚地表明您希望从初始收集开始并过滤掉不良项目。

标签: c# dictionary


【解决方案1】:

你可以

C# 的集合初始化器只是对任何名为 Add 的方法(包括扩展方法)的语法糖。无论您在每个项目的 {} 之间使用的任何 C# 语法都将转换为 Add 方法参数。

所以这两个语句几乎相同(如果在初始化程序完成之前抛出异常,collection-object-lifetime 会略有不同,但这不值得担心)

// Manually:
Dictionary<String,Foo> dict = new Dictionary<String,Foo>();
dict.Add( "foo1", new Foo(1) );
dict.Add( "foo2", new Foo(2) );
dict.Add( "foo3", new Foo(3) );
dict.Add( "foo4", new Foo(4) );

// Collection-initializer:
Dictionary<String,Foo> dict = new Dictionary<String,Foo>()
{
    { "foo1", new Foo(1) },
    { "foo2", new Foo(2) },
    { "foo3", new Foo(3) },
    { "foo4", new Foo(4) }
};

(这也意味着如果你认为对象初始化器比重复调用.Add() 以某种方式更有效,或者它们甚至会导致 C++ constexpr-style集合的编译时预初始化......不幸的是你错了:集合初始化器总是有至少O(n)运行时 - 如果你需要更有效的初始化考虑使用数组支持数据结构,因此您可以使用预初始化数组)。

因此,如果您想要一个条件初始化器,首先为您的集合类型(在本例中为 Dictionary&lt;String,dynamic&gt;)定义扩展方法,并带有一个额外的 Boolean condition 参数:

public static class MyDictionaryExtensions
{
    public static void Add( this Dictionary<String,dynamic> dict, String key, dynamic value, Boolean condition )
    {
        if( condition )
        {
            dict.Add( key, value );
        }
    }
}

那么你可以这样使用它:

// Pretend these values are retrieved from a database or something
string firstName = "John";
string lastName = "Smith";

List<string> listHobbies = new List<string> () {"hockey","soccer"};

var dict = new Dictionary<string, dynamic>() 
{
    { "firstName", firstName   },
    { "lastName" , lastName    },
    { "hobbies"  , listHobbies, listHobbies.Count > 0 }
};

更好的是:您可以使其通用(无论如何您可能不应该使用dynamic):

public static class MyDictionaryExtensions
{
    public static void Add<TKey,TValue>( this Dictionary<TKey,TValue> dict, TKey key, TValue value, Boolean condition )
        where TKey : notnull
    {
        if( condition )
        {
            dict.Add( key, value );
        }
    }
}

这是它在 LinqPad 中工作的截图证明:


但是为什么要停在那里呢?您不仅限于Boolean condition 参数:例如,您可以有一些复杂的逻辑来检查this dict 的状态,或者使用Func&lt;Boolean&gt; 在运行时确定每个项目的条件。

不过有一个小警告:您的 Add 扩展方法的参数不能完全匹配原始的 Add 方法,否则由于扩展方法的方式,您的扩展方法将不会被选中总是具有较低的优先级。您也不能通过使用显式参数名称来强制选择您的扩展方法(集合初始化器没有命名参数),但您可以使用自定义参数作为第三个参数struct 类型(因此它实际上具有零成本)。

例如,这是一个扩展,我使用来自失败的HttpClient 请求的信息简洁地填充Exception.Data 的字典,如果值为null,则跳过responseBody

internal struct OnlyIfBodyIsNotNull
{
}

internal static class DictionaryExtensions
{
    public static void Add( this IDictionary exDataDict, String key, String? body, OnlyIfBodyIsNotNull _ )
    {
        if( body != null )
        {
            exDataDict.Add( key, body );
        }
    }
}

这样使用:

public static InvalidOperationException CreateException( String message, HttpResponseHeaders responseHeaders, HttpContentHeaders contentHeaders, String? responseBody = null )
{
    return new InvalidOperationException( message )
    {
        Data =
        {
            { "Response.Headers"       , responseHeaders.ToString() },
            { "Response.ContentHeaders", contentHeaders .ToString() },
            { "Response.Body"          , responseBody              , default(OnlyIfBodyIsNotNull) }
        }
    };
}

【讨论】:

  • 很好的答案。我知道集合初始化的语法糖,但我真的不知道您可以以扩展方法的形式编写自己的。
【解决方案2】:

这有什么问题?

var dict = new Dictionary<string, dynamic>() 
{
    {"firstName", firstName},
    {"lastName", lastName}
};
if (listHobbies.Any())
    dict.Add("hobbies", listHobbies);

【讨论】:

  • 它在声明后改变集合,这是我试图避免的。然而,我正在尝试做的事情目前在 C# 中似乎不可能,但我认为我找到了一个更好看的替代方案,将在一分钟内编辑 OP。
  • @Fano 为什么不将突变部分移动到一个方法中,然后将 dict 设为只读,这样您仍然可以保持不变性。初始化后不应该更改集合是否有任何具体原因?
  • 没有什么特别的原因,我一直在学习函数式编程原理并尝试将它们应用到我的其他项目中,在 C# 中这样做可能不是最佳实践,这种语言不是考虑到这些事情(我想)。尽管如此,我只是好奇这是否可能。
  • 此外,如果本地输入为IReadOnlyDictionary&lt;String,dynamic&gt;,则此方法将不起作用。
猜你喜欢
  • 1970-01-01
  • 2011-12-14
  • 2018-10-05
  • 1970-01-01
  • 2011-03-04
  • 2012-10-14
  • 1970-01-01
  • 2017-12-27
  • 2023-03-18
相关资源
最近更新 更多