我一直在尝试找一个“干净”的模式来处理带有匿名类型的.SelectMany
,而不总是想要返回结果。我最常见的用例如下:
.SelectMany
例如,逻辑可能如下所示:
//c is a customer
var context = GetContextForCustomer(c);
// look up some data, myData using the context connection
if (someCondition)
return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
else
return null;
这可以作为foreach声明实现:
var results = new List<WhatType?>();
foreach (var c in customers) {
var context = GetContextForCustomer(c);
if (someCondition)
results.AddRange(myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 }));
}
或者可以使用预先过滤了.SelectMany
的{{1}}来实施:
.Where
这两种方法都存在问题。 customers
.Where(c => someCondition)
.AsParallel()
.SelectMany(c => {
var context = GetContextForCustomer(c);
return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
})
.ToList();
解决方案需要初始化foreach
来存储结果,您必须定义类型。带有List
的{{1}}通常不切实际,因为.SelectMany
的逻辑相当复杂,并且取决于某些数据查找。所以我的理想解决方案看起来像这样:
.Where
我在someCondition
行中放置什么来跳过返回值?没有任何解决方案我可以提出工作或理想:
customers
.AsParallel()
.SelectMany(c => {
var context = GetContextForCustomer(c);
if (someCondition)
return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
else
continue? return null? return empty list?
})
.ToList();
无法编译,因为它不是活动的else
循环continue
会导致foreach
return null
空列表要求我再次初始化匿名类型列表。有没有办法完成上述干净,简单,整洁,满足我所有(挑剔)的要求?
答案 0 :(得分:2)
不知道someCondition
和myData
的样子......
为什么你不仅仅Select
和Where
上下文:
customers
.Select(c => GetContextForCustomer(c))
.Where(ctx => someCondition)
.SelectMany(ctx =>
myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
customer
和context
,所以你可以这样做:
customers
.Select(c => new { Customer = c, Context = GetContextForCustomer(c) })
.Where(x => someCondition(x.Context))
.SelectMany(x =>
myData.Select(d => new { CustomerID = x.Customer, X1 = d.x1, X2 = d.x2 });
答案 1 :(得分:1)
您可以尝试以下操作:
customers
.AsParallel()
.SelectMany(c => {
var context = GetContextForCustomer(c);
if (someCondition)
return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
else
return Enumerable.Empty<int>().Select(x => new { CustomerID = 0, X1 = "defValue", X2 = "defValue" });
})
.ToList();
具有相同属性集(相同名称和类型)的所有匿名类型由编译器组合成一个匿名类。这就是为什么Select
和Enumerable.Empty
上的T
将返回相同的{{1}}。
答案 2 :(得分:1)
您可以返回空Enumerable<dynamic>
。这是一个例子(虽然没有你的客户和someCondition
,因为我不知道它们是什么,而是你的例子的一般形式):
new int[] { 1, 2, 3, 4 }
.AsParallel()
.SelectMany(i => {
if (i % 2 == 0)
return Enumerable.Repeat(new { i, squared = i * i }, i);
else
return Enumerable.Empty<dynamic>();
})
.ToList();
因此,对于您的对象和someCondition
,它看起来像
customers
.AsParallel()
.SelectMany(c => {
var context = GetContextForCustomer(c);
if (someCondition)
return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
else
return Enumerable.Empty<dynamic>();
})
.ToList();
答案 3 :(得分:1)
您可以创建自己的SelectMany
LINQ方法变体,该方法支持null
s:
public static class EnumerableExtensions
{
public static IEnumerable<TResult> NullableSelectMany<TSource, TResult> (
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector)
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
foreach (TSource item in source) {
IEnumerable<TResult> results = selector(item);
if (results != null) {
foreach (TResult result in results)
yield return result;
}
}
}
}
现在,您可以在null
lambda中返回selector
。
答案 4 :(得分:0)
接受的答案返回dynamic
。最干净的是将过滤逻辑移动到Where
,这使得整个事情在linq上下文中看起来更好。既然你明确地在问题中排除了这一点,并且我不是在linq调用中多行写的代表的粉丝,那么我会尝试this,但是可以认为它更为hacky。
var results = new
{
customerID = default(int), //notice the casing of property names
x1 = default(U), //whatever types they are
x2 = default(V)
}.GetEmptyListOfThisType();
foreach (var customerID in customers) {
var context = GetContextForCustomer(customerID);
if (someCondition)
results.AddRange(myData.Select(x => new { customerID, x.x1, x.x2 }));
}
public static List<T> GetEmptyListOfThisType<T>(this T item)
{
return new List<T>();
}
注意适当使用与其他变量名称一致的属性名称,因此您不必在Select
调用中第二次写入属性名称。