C# LINQ查询Select投影,函数编程
C# LINQ query Select projection, Function programming
public class Factory
{
public string Number { get; set; }
public List<OrganizationUnit> Units;
}
public class OrganizationUnit
{
public string Name { get; set; }
public string Id { get; set; }
public bool ?IsActive { get; set; }
}
public static class LocalisationRepo
{
public static List<Factory> GetFactories()
{
List<Factory> fa = new List<Factory>();
Factory factory = new Factory() { Number="F10", Units = new List<OrganizationUnit> {
new OrganizationUnit()
{ Id = "001", Name = "Quality", IsActive = false},
new OrganizationUnit() { Id = "002", Name = "Line 3", IsActive=null } ,
new OrganizationUnit { Id="003", Name="IT", IsActive=true } } };
Factory factory2 = new Factory()
{
Number = "F11",
Units = new List<OrganizationUnit> {
new OrganizationUnit()
{ Id = "001", Name = "Quality", IsActive = true},
new OrganizationUnit() { Id = "002", Name = "Line 3", IsActive=true } ,
new OrganizationUnit { Id="003", Name="IT", IsActive=true } }
};
fa.Add(factory);
fa.Add(factory2);
return fa;
}
}
有一个linq查询
var factories = LocalisationRepo.GetFactories();
var fa = factories.SelectMany(f => f.Units.Where(u => (u.IsActive ?? false) == false), (f, u) => f).Distinct();
foreach (var item in fa)
{
Console.WriteLine(item.Number);
}
第一部分f => f.Units.Where(u => (u.IsActive ?? false) == false)
给我们 IEnumerable<OrganizationUnit>
第二个参数 (f, u) => f
(应用于中间序列的每个元素的转换函数)
“将序列的每个元素投影到 IEnumerable<Out T>
”
我的问题是,当第一个参数的“输出”是 IEnumerable<OrganizationUnit>
时,这个 selector/delegate 如何实现从 IEnumerable<OrganizationUnit>
到 IEnumerable<Factory>
的转换
应该如何看待/理解?
我们知道 f 是一个工厂,但是“中间结果”是 OrganizationUnit 所以...如何?关于函数编程的一些理论?
我想要具有不活动 OrganisationUnits 的工厂 IsActive=false;
但我不是在问如何找到结果,因为我的例子工作正常。
我想知道它是如何工作的,为什么...
与 Microsoft 示例相同 https://docs.microsoft.com/en-US/dotnet/api/system.linq.enumerable.selectmany?view=net-6.0
我们可以查询
变种查询=
宠物主人
.SelectMany(po => po.Pets.Where(p => p.StartsWith("S")), (po, p) => po);
SelectMany 运算符用于 select 来自名为 Nested Collection 的集合的元素。
SelectMany returns 来自嵌套集合的单个结果
您可以使用 SelectMany 从工厂获取每个 OrganizationUnit 行。
var fa = factories.SelectMany(f => f.Units.Where(u => !(u.IsActive??false)), (factory, unit) => unit);
您正在尝试反方向。
重要 不要使用 .Distinct() 来修复重复项。一开始就不应该有重复项。
解法:
var fa = factories.Where( factory => factory.Units.Any(unit => (unit.IsActive?? false) == false));
我认为这里的混淆与(不必要的)使用 SelectMany
来过滤工厂有关。
在此声明中:
var fa = factories.SelectMany(f => f.Units.Where(u => (u.IsActive ?? false) == false), (f, u) => f);
(f, u) => f
这个参数基本就是忽略了selectmany挑的东西(也就是u
),反正都是挑原厂f
所以最后,你的陈述等同于:
```
var fa = factories.Select(f => f);
```
这基本上是一个空操作。
所以上面的情况不完全是这样。事实证明,您的实际查询等同于
var fa = factories.Where(f => f.Units.Any(u => (u.IsActive ?? false) == false);
这是因为,对于每个工厂,SelectMany
重载将选择您提供的每个匹配的 Unit
,将其作为第三个参数的 func 参数传递给您,然后等待然后您可以将其转换为您想要的任何东西。因此它希望您将 (Factory,Unit)
对转换为 some T
。您决定避免转换,而是选择第一个元素 Factory
f。然后,SelectMany
会将所有此类 lambda 执行的结果组合成最终序列,在您的情况下,仅包含工厂。如果您列表中的任何工厂包含多个禁用单元,它将在最终结果中出现多次,这就是为什么您必须向其添加 Distinct
以使查询“有意义”。
如果你想过滤“至少有一个非活跃单位的工厂”,你会做这样的事情:
var fa = factories.Where(f => f.Units.Any(u => (u.IsActive ?? false) == false);
如果你想要“所有单元都处于非活动状态的工厂”,你可以这样做:
var fa = factories.Where(f => f.Units.All(u => (u.IsActive ?? false) == false);
We know that f is a Factory, but "intermediate result" is OrganizationUnit so ... HOW ? Some theory about function programming?
“中间结果”是不是 OrganizationUnit
:有了这个重载,中间结果实际上是一对(Factory,OrganizationResult)
,你 通过选择 f
作为结果“转换”为 Factory
。没什么特别的。
SelectMany
应该用于扁平化结果,您似乎不需要。
How should it be considered / understood?
您正在对工厂列表执行 SelectMany 操作,并告诉 SM 您希望它压平每个工厂中的单位。第二个参数表示一个函数,它同时接受正在迭代的当前工厂和正在迭代的单元之一。
listOfFactories.SelectMany(aFactory => aFactory.listOfTheUnits, (theCurrentFactory, oneOfTheUnitsOfTheCurrentFactory) => ...)
或在非代表条款中
listOfFactories.SelectMany(GetUnitsFromAFactory, DoSomethingWithAFactoryAndUnitAndGetAnOutput)
SelectMany 在概念上只是一对嵌套的 foreach 循环;用这些术语来说,它可能是这样的:
var output = List<SomeOutput>();
foreach(var aFactory in listOfFactories){
var theCurrentFactory = aFactory;
var someEnumerable = GetUnitsFromAFactory();
foreach(var oneOfTheUnitsOfTheCurrentFactory in someEnumerable){
output.Add(DoSomethingWithAFactoryAndUnitAndGetAnOutput(theCurrentFactory, oneOfTheUnitsOfTheCurrentFactory);
}
}
//methods
IEnumerable<OrganizationUnit> GetUnitsFromAFactory(Factory f){
return f.listOfTheUnits;
}
SomeOutput DoSomethingWithAFactoryAndUnitAndGetAnOutput(Factory f, Unit u) {
...
}
你的代码令人困惑的是你只 return 来自“DoSomethingWithAFactoryAndUnitAndGetAnOutput”的工厂,所以如果你有 3 个分别有 10、11 和 12 个单元的工厂,你最终会得到一个列表33 家工厂中,第一家工厂有 10 次重复,第二家工厂有 11 次重复,第三家工厂有 12 次重复。现在我们(希望)很高兴 SelectMany 是一个嵌套循环对,让我们添加关于单元处于非活动状态的其他位
var out = new List<Factory>
foreach(var f in factories)
foreach(var u in f.Units)
if(unit.IsActive ?? false == false)
out.Add(f);
你看,你已经多次添加工厂,每添加一个不活动的单元一次
然后您将它们分开以折叠它们,这样整个 SelectMany 就成了一种浪费的练习,您可以改为只请求具有不活动单元的工厂。在简单的循环术语中,可能看起来像:
var out = new List<Factory>
foreach(var f in factories)
foreach(var u in f.Units)
if(unit.IsActive ?? false == false){
out.Add(f);
break;
}
在添加单个工厂后添加中断停止(遇到第一个不活动的单元)。在渐进式添加 LINQ 术语中,这更像是在做
foreach(var f in factories)
if(f.Units.Any(unit => unit.IsActive ?? false == false))
out.Add(f);
这是
factories.Where(f => f.Units.Any(unit => unit.IsActive ?? false == false))
我不认为 SelectMany
正在做您认为正在做的事情。我想你认为它通过某种从单位集合返回工厂的投影将单位过滤到 IsActive
是 false
的工厂返回工厂。这是不正确的。
SelectMany
的第二个选择器是 resultSelector
,您在其中传递:
(f, u) => f
其中 f
和 u
是源集合的项目,以及前一个参数投影的集合的项目(您的 Where
结果)。这意味着对于满足 Where
条件 (u
) 的任何单元,它 returns 包含它的工厂 (f
).这就是您必须调用 Distinct
的原因,因为您实际上是在为每个匹配的单元 获取工厂 。如果一家工厂有两个符合标准的单位,那么您将两次返回同一家工厂。 Unique
从结果中删除那些重复的工厂,但工厂本身(意味着其中的单元连接)没有改变。
为了“过滤”子集合,您需要重新投影工厂:
var fa = factories.Select(f => new Factory(
{
Number = f.Number,
Units = f.Units.Where(u => (u.IsActive ?? false) == false).ToList();
}
)
);
或者,循环遍历工厂并 删除 IsActive
为 true
的单元。
public class Factory
{
public string Number { get; set; }
public List<OrganizationUnit> Units;
}
public class OrganizationUnit
{
public string Name { get; set; }
public string Id { get; set; }
public bool ?IsActive { get; set; }
}
public static class LocalisationRepo
{
public static List<Factory> GetFactories()
{
List<Factory> fa = new List<Factory>();
Factory factory = new Factory() { Number="F10", Units = new List<OrganizationUnit> {
new OrganizationUnit()
{ Id = "001", Name = "Quality", IsActive = false},
new OrganizationUnit() { Id = "002", Name = "Line 3", IsActive=null } ,
new OrganizationUnit { Id="003", Name="IT", IsActive=true } } };
Factory factory2 = new Factory()
{
Number = "F11",
Units = new List<OrganizationUnit> {
new OrganizationUnit()
{ Id = "001", Name = "Quality", IsActive = true},
new OrganizationUnit() { Id = "002", Name = "Line 3", IsActive=true } ,
new OrganizationUnit { Id="003", Name="IT", IsActive=true } }
};
fa.Add(factory);
fa.Add(factory2);
return fa;
}
}
有一个linq查询
var factories = LocalisationRepo.GetFactories();
var fa = factories.SelectMany(f => f.Units.Where(u => (u.IsActive ?? false) == false), (f, u) => f).Distinct();
foreach (var item in fa)
{
Console.WriteLine(item.Number);
}
第一部分f => f.Units.Where(u => (u.IsActive ?? false) == false)
给我们 IEnumerable<OrganizationUnit>
第二个参数 (f, u) => f
(应用于中间序列的每个元素的转换函数)
“将序列的每个元素投影到 IEnumerable<Out T>
”
我的问题是,当第一个参数的“输出”是 IEnumerable<OrganizationUnit>
时,这个 selector/delegate 如何实现从 IEnumerable<OrganizationUnit>
到 IEnumerable<Factory>
的转换
应该如何看待/理解?
我们知道 f 是一个工厂,但是“中间结果”是 OrganizationUnit 所以...如何?关于函数编程的一些理论?
我想要具有不活动 OrganisationUnits 的工厂 IsActive=false; 但我不是在问如何找到结果,因为我的例子工作正常。 我想知道它是如何工作的,为什么...
与 Microsoft 示例相同 https://docs.microsoft.com/en-US/dotnet/api/system.linq.enumerable.selectmany?view=net-6.0
我们可以查询 变种查询= 宠物主人 .SelectMany(po => po.Pets.Where(p => p.StartsWith("S")), (po, p) => po);
SelectMany 运算符用于 select 来自名为 Nested Collection 的集合的元素。
SelectMany returns 来自嵌套集合的单个结果 您可以使用 SelectMany 从工厂获取每个 OrganizationUnit 行。
var fa = factories.SelectMany(f => f.Units.Where(u => !(u.IsActive??false)), (factory, unit) => unit);
您正在尝试反方向。
重要 不要使用 .Distinct() 来修复重复项。一开始就不应该有重复项。
解法:
var fa = factories.Where( factory => factory.Units.Any(unit => (unit.IsActive?? false) == false));
我认为这里的混淆与(不必要的)使用 SelectMany
来过滤工厂有关。
在此声明中:
var fa = factories.SelectMany(f => f.Units.Where(u => (u.IsActive ?? false) == false), (f, u) => f);
(f, u) => f
这个参数基本就是忽略了selectmany挑的东西(也就是u
),反正都是挑原厂f
所以上面的情况不完全是这样。事实证明,您的实际查询等同于
var fa = factories.Where(f => f.Units.Any(u => (u.IsActive ?? false) == false);
这是因为,对于每个工厂,SelectMany
重载将选择您提供的每个匹配的 Unit
,将其作为第三个参数的 func 参数传递给您,然后等待然后您可以将其转换为您想要的任何东西。因此它希望您将 (Factory,Unit)
对转换为 some T
。您决定避免转换,而是选择第一个元素 Factory
f。然后,SelectMany
会将所有此类 lambda 执行的结果组合成最终序列,在您的情况下,仅包含工厂。如果您列表中的任何工厂包含多个禁用单元,它将在最终结果中出现多次,这就是为什么您必须向其添加 Distinct
以使查询“有意义”。
如果你想过滤“至少有一个非活跃单位的工厂”,你会做这样的事情:
var fa = factories.Where(f => f.Units.Any(u => (u.IsActive ?? false) == false);
如果你想要“所有单元都处于非活动状态的工厂”,你可以这样做:
var fa = factories.Where(f => f.Units.All(u => (u.IsActive ?? false) == false);
We know that f is a Factory, but "intermediate result" is OrganizationUnit so ... HOW ? Some theory about function programming?
“中间结果”是不是 OrganizationUnit
:有了这个重载,中间结果实际上是一对(Factory,OrganizationResult)
,你 通过选择 f
作为结果“转换”为 Factory
。没什么特别的。
SelectMany
应该用于扁平化结果,您似乎不需要。
How should it be considered / understood?
您正在对工厂列表执行 SelectMany 操作,并告诉 SM 您希望它压平每个工厂中的单位。第二个参数表示一个函数,它同时接受正在迭代的当前工厂和正在迭代的单元之一。
listOfFactories.SelectMany(aFactory => aFactory.listOfTheUnits, (theCurrentFactory, oneOfTheUnitsOfTheCurrentFactory) => ...)
或在非代表条款中
listOfFactories.SelectMany(GetUnitsFromAFactory, DoSomethingWithAFactoryAndUnitAndGetAnOutput)
SelectMany 在概念上只是一对嵌套的 foreach 循环;用这些术语来说,它可能是这样的:
var output = List<SomeOutput>();
foreach(var aFactory in listOfFactories){
var theCurrentFactory = aFactory;
var someEnumerable = GetUnitsFromAFactory();
foreach(var oneOfTheUnitsOfTheCurrentFactory in someEnumerable){
output.Add(DoSomethingWithAFactoryAndUnitAndGetAnOutput(theCurrentFactory, oneOfTheUnitsOfTheCurrentFactory);
}
}
//methods
IEnumerable<OrganizationUnit> GetUnitsFromAFactory(Factory f){
return f.listOfTheUnits;
}
SomeOutput DoSomethingWithAFactoryAndUnitAndGetAnOutput(Factory f, Unit u) {
...
}
你的代码令人困惑的是你只 return 来自“DoSomethingWithAFactoryAndUnitAndGetAnOutput”的工厂,所以如果你有 3 个分别有 10、11 和 12 个单元的工厂,你最终会得到一个列表33 家工厂中,第一家工厂有 10 次重复,第二家工厂有 11 次重复,第三家工厂有 12 次重复。现在我们(希望)很高兴 SelectMany 是一个嵌套循环对,让我们添加关于单元处于非活动状态的其他位
var out = new List<Factory>
foreach(var f in factories)
foreach(var u in f.Units)
if(unit.IsActive ?? false == false)
out.Add(f);
你看,你已经多次添加工厂,每添加一个不活动的单元一次
然后您将它们分开以折叠它们,这样整个 SelectMany 就成了一种浪费的练习,您可以改为只请求具有不活动单元的工厂。在简单的循环术语中,可能看起来像:
var out = new List<Factory>
foreach(var f in factories)
foreach(var u in f.Units)
if(unit.IsActive ?? false == false){
out.Add(f);
break;
}
在添加单个工厂后添加中断停止(遇到第一个不活动的单元)。在渐进式添加 LINQ 术语中,这更像是在做
foreach(var f in factories)
if(f.Units.Any(unit => unit.IsActive ?? false == false))
out.Add(f);
这是
factories.Where(f => f.Units.Any(unit => unit.IsActive ?? false == false))
我不认为 SelectMany
正在做您认为正在做的事情。我想你认为它通过某种从单位集合返回工厂的投影将单位过滤到 IsActive
是 false
的工厂返回工厂。这是不正确的。
SelectMany
的第二个选择器是 resultSelector
,您在其中传递:
(f, u) => f
其中 f
和 u
是源集合的项目,以及前一个参数投影的集合的项目(您的 Where
结果)。这意味着对于满足 Where
条件 (u
) 的任何单元,它 returns 包含它的工厂 (f
).这就是您必须调用 Distinct
的原因,因为您实际上是在为每个匹配的单元 获取工厂 。如果一家工厂有两个符合标准的单位,那么您将两次返回同一家工厂。 Unique
从结果中删除那些重复的工厂,但工厂本身(意味着其中的单元连接)没有改变。
为了“过滤”子集合,您需要重新投影工厂:
var fa = factories.Select(f => new Factory(
{
Number = f.Number,
Units = f.Units.Where(u => (u.IsActive ?? false) == false).ToList();
}
)
);
或者,循环遍历工厂并 删除 IsActive
为 true
的单元。