我怎样才能改进 LINQ?
How can I improve LINQ?
所以这个问题是关于静态集合的 .Select()
语句(即不是 Select()
、Where()
或其他 LINQ 操作的结果,例如 List
array
).
我的印象是,当使用 .Select()
或其他非过滤、非排序方法时,.ElementAt()
会从原始集合中取出元素,然后 运行通过.Select
。我认为这是最好的方法,因为 .ElementAt()
只有 returns 一个元素,LINQ 不缓存任何东西,所以其他生成的项目被丢弃。
举个例子:
var original = Enumerable.Range(0, 1000);
var listWithADifficultSelect = original.Select(aMethod);
var onlyOneItem = listWithADifficultSelect.ElementAt(898);
object aMethod(int number) {
// Gets the item from some kind of database, difficult operation
// Takes at least a few milliseconds
return new object();
}
从更大的角度来看,如果我有一个包含 20K 个项目的列表,我只需要第 nth 个项目,但我执行了相当繁重的 .Select()
, 我希望 .Select()
仅投影列表中的一项。
所以我有两个问题:
- 为什么要这样建?
- 有没有办法构建一个改进的
.Select()
来满足我的要求?
如果我对你的问题的理解正确,如果你只需要第 898 个元素,你不希望 LINQ 为前 897 个元素调用 aMethod
。
那你为什么不这样称呼它呢:
var onlyOneItem = aMethod(original.ElementAt(898));
如果你想得到几个特定的元素,只是不想让 LINQ 一直重新计算 aMethod
,那么把你的结果变成一个 List
或数组:
var listWithADifficultSelect = original.Select(aMethod).ToList(); // or ToArray();
因此 Select
及其所有 aMethod
调用只执行一次,您可以访问所有元素而无需重新调用 aMethod
。
如果您想编写自己的 LINQ 方法来完成比 LINQ 已经做的更多的事情,您可以轻松实现自己的扩展:
public static class MyLinq
{
public static IEnumerable<TResult> MySelect<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,TResult> selector)
{
// implement yourself
}
public static TSource MyElementAt<TSource>(this IEnumerable<TSource>, int index)
{
// implement yourself
}
}
甚至可以很好地转换为 SQL(如果这是一个问题)的通用解决方案是使用 Skip
和 Take
。您可以跳过前 n-1
项,然后从原始 IEnumerable
(或 IQueryable
)中取出 1
。
var original = Enumerable.Range(0, 1000);
var onlyOneItem = original.Skip(898 - 1).Take(1).Select(aMethod);
Skip
和 Take
是 Linq 等价于 SQL 的 OFFSET
和 LIMIT
.
在像您的示例这样的简化情况下,您不会看到任何性能改进,但如果您在实际应用程序中有一个昂贵的查询,这样您就可以避免获取任何不必要的元素
所以这个问题是关于静态集合的 .Select()
语句(即不是 Select()
、Where()
或其他 LINQ 操作的结果,例如 List
array
).
我的印象是,当使用 .Select()
或其他非过滤、非排序方法时,.ElementAt()
会从原始集合中取出元素,然后 运行通过.Select
。我认为这是最好的方法,因为 .ElementAt()
只有 returns 一个元素,LINQ 不缓存任何东西,所以其他生成的项目被丢弃。
举个例子:
var original = Enumerable.Range(0, 1000);
var listWithADifficultSelect = original.Select(aMethod);
var onlyOneItem = listWithADifficultSelect.ElementAt(898);
object aMethod(int number) {
// Gets the item from some kind of database, difficult operation
// Takes at least a few milliseconds
return new object();
}
从更大的角度来看,如果我有一个包含 20K 个项目的列表,我只需要第 nth 个项目,但我执行了相当繁重的 .Select()
, 我希望 .Select()
仅投影列表中的一项。
所以我有两个问题:
- 为什么要这样建?
- 有没有办法构建一个改进的
.Select()
来满足我的要求?
如果我对你的问题的理解正确,如果你只需要第 898 个元素,你不希望 LINQ 为前 897 个元素调用 aMethod
。
那你为什么不这样称呼它呢:
var onlyOneItem = aMethod(original.ElementAt(898));
如果你想得到几个特定的元素,只是不想让 LINQ 一直重新计算 aMethod
,那么把你的结果变成一个 List
或数组:
var listWithADifficultSelect = original.Select(aMethod).ToList(); // or ToArray();
因此 Select
及其所有 aMethod
调用只执行一次,您可以访问所有元素而无需重新调用 aMethod
。
如果您想编写自己的 LINQ 方法来完成比 LINQ 已经做的更多的事情,您可以轻松实现自己的扩展:
public static class MyLinq
{
public static IEnumerable<TResult> MySelect<TSource,TResult>(this IEnumerable<TSource>, Func<TSource,TResult> selector)
{
// implement yourself
}
public static TSource MyElementAt<TSource>(this IEnumerable<TSource>, int index)
{
// implement yourself
}
}
甚至可以很好地转换为 SQL(如果这是一个问题)的通用解决方案是使用 Skip
和 Take
。您可以跳过前 n-1
项,然后从原始 IEnumerable
(或 IQueryable
)中取出 1
。
var original = Enumerable.Range(0, 1000);
var onlyOneItem = original.Skip(898 - 1).Take(1).Select(aMethod);
Skip
和 Take
是 Linq 等价于 SQL 的 OFFSET
和 LIMIT
.
在像您的示例这样的简化情况下,您不会看到任何性能改进,但如果您在实际应用程序中有一个昂贵的查询,这样您就可以避免获取任何不必要的元素