我有一个结构字典,其中一个成员是一个列表,其中包含适用于每个字典项目的不同元素。
我想针对每个项目加入这些元素,以便过滤它们和/或按元素对它们进行分组。
在 SQL 中,我熟悉连接表/查询以根据需要获取多行,但我是 C#/Linq 的新手。由于“列”可以是已经与适当的字典项相关联的对象/列表,我想知道如何使用它们来执行连接?
这是结构的示例:
name elements
item1 list: elementA
item2 list: elementA, elementB
我想要一个给出这个输出的查询(count = 3)
name elements
item1 elementA
item2 elementA
item2 elementB
最终,将它们分组如下:
element count
ElementA 2
ElementB 1
这是我开始计算字典项目的代码。
public struct MyStruct
{
public string name;
public List<string> elements;
}
private void button1_Click(object sender, EventArgs e)
{
MyStruct myStruct = new MyStruct();
Dictionary<String, MyStruct> dict = new Dictionary<string, MyStruct>();
// Populate 2 items
myStruct.name = "item1";
myStruct.elements = new List<string>();
myStruct.elements.Add("elementA");
dict.Add(myStruct.name, myStruct);
myStruct.name = "item2";
myStruct.elements = new List<string>();
myStruct.elements.Add("elementA");
myStruct.elements.Add("elementB");
dict.Add(myStruct.name, myStruct);
var q = from t in dict
select t;
MessageBox.Show(q.Count().ToString()); // Returns 2
}
编辑:我真的不需要输出是一本字典。我用它来存储我的数据,因为它运行良好并且可以防止重复(我确实有唯一的 item.name,我将其存储为键)。但是,出于过滤/分组的目的,我想它可能是一个没有问题的列表或数组。之后我总是可以做 .ToDictionary where key = item.Name。
最佳答案
var q = from t in dict
from v in t.Value.elements
select new { name = t.Key, element = v };
这里的方法是Enumerable.SelectMany。使用扩展方法语法:
var q = dict.SelectMany(t => t.Value.elements.Select(v => new { name = t.Key, element = v }));
编辑
请注意,您也可以使用 t.Value.name
以上,而不是 t.Key
,因为这些值是相等的。
那么,这里发生了什么?
查询理解语法可能是最容易理解的;您可以编写等效的迭代器 block 来查看发生了什么。但是,我们不能简单地使用匿名类型来做到这一点,因此我们将声明一个类型以返回:
class NameElement
{
public string name { get; set; }
public string element { get; set; }
}
IEnumerable<NameElement> GetResults(Dictionary<string, MyStruct> dict)
{
foreach (KeyValuePair<string, MyStruct> t in dict)
foreach (string v in t.Value.elements)
yield return new NameElement { name = t.Key, element = v };
}
扩展方法语法如何(或者,这里真正发生了什么)?
(这在一定程度上受到 Eric Lippert 在 https://stackoverflow.com/a/2704795/385844 的帖子的启发;我有一个更复杂的解释,然后我阅读了它,并想出了这个:)
假设我们想避免声明 NameElement 类型。我们可以通过传入一个函数来使用匿名类型。我们将从这里更改调用:
var q = GetResults(dict);
为此:
var q = GetResults(dict, (string1, string2) => new { name = string1, element = string2 });
lambda 表达式 (string1, string2) => new { name = string1, element = string2 }
表示一个接受 2 个字符串的函数——由参数列表 (string1, string2)
定义-- 并返回用这些字符串初始化的匿名类型的实例 -- 由表达式 new { name = string1, element = string2 }
定义.
对应的实现是这样的:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<string, string, T> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (string e in pair.Value.elements)
yield return resultSelector.Invoke(t.Key, v);
}
类型推断允许我们在不指定 T
的情况下调用此函数按名字。这很方便,因为(就我们 C# 程序员所知),我们使用的类型没有名称:它是匿名的。
注意变量t
现在是pair
, 以避免与类型参数 T
混淆, 和 v
现在是e
,对于“元素”。我们还将第一个参数的类型更改为其基本类型之一,IEnumerable<KeyValuePair<string, MyStruct>>
.它比较冗长,但它使方法更有用,最终会有所帮助。由于该类型不再是字典类型,我们还将参数名称从 dict
更改为至 pairs
.
我们可以进一步概括这一点。第二个foreach
具有将键值对投影到类型 T 的序列的效果。整个效果可以封装在一个函数中;委托(delegate)类型将是 Func<KeyValuePair<string, MyStruct>, T>
.第一步是重构方法,这样我们就有了一个转换元素 pair
的语句。成一个序列,使用 Select
调用 resultSelector
的方法代表:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<string, string, T> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (T result in pair.Value.elements.Select(e => resultSelector.Invoke(pair.Key, e))
yield return result;
}
现在我们可以轻松更改签名:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<KeyValuePair<string, MyStruct>, IEnumerable<T>> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (T result in resultSelector.Invoke(pair))
yield return result;
}
调用站点现在看起来像这样;注意 lambda 表达式现在如何合并我们在更改其签名时从方法体中删除的逻辑:
var q = GetResults(dict, pair => pair.Value.elements.Select(e => new { name = pair.Key, element = e }));
为了使该方法更有用(及其实现不那么冗长),让我们替换类型 KeyValuePair<string, MyStruct>
带有类型参数,TSource
.我们将同时更改其他一些名称:
T -> TResult
pairs -> sourceSequence
pair -> sourceElement
而且,为了好玩,我们将把它作为一个扩展方法:
static IEnumerable<TResult> GetResults<TSource, TResult>(
this IEnumerable<TSource> sourceSequence,
Func<TSource, IEnumerable<TResult>> resultSelector)
{
foreach (TSource sourceElement in sourceSequence)
foreach (T result in resultSelector.Invoke(pair))
yield return result;
}
就这样:SelectMany!好吧,这个函数仍然有一个错误的名字,实际的实现包括验证源序列和选择器函数是非空的,但这是核心逻辑。
来自 MSDN : SelectMany
“将序列的每个元素投影到 IEnumerable 并将结果序列展平为一个序列。”
关于c# - Linq 查询以加入结构中的列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9494362/