Linq 在哪里有奇怪的情况
Posted
技术标签:
【中文标题】Linq 在哪里有奇怪的情况【英文标题】:Linq where with strange condition 【发布时间】:2021-10-21 13:41:47 【问题描述】:我很难理解这行代码,但我并没有摆脱它。
指令如下
var targetHandles = target.Where(t => true);
在具有这些签名的函数内部
public static void Merge<T>(ObservableCollection<T> target, ObservableCollection<T> source) where T : DtoBase
提前感谢您的回答
瑞克
【问题讨论】:
Enumerable.Where Method。该方法是通过使用延迟执行来实现的。立即返回值是一个存储执行操作所需的所有信息的对象。在通过直接调用其GetEnumerator
方法或使用foreach
枚举对象之前,不会执行此方法表示的查询。这个条件真的没有任何意义,除非唯一的目标是获得WhereEnumerableIterator
一种可能是 targetHandles
稍后将通过对 LINQ 方法的额外调用进行修改,因此您希望其类型为 IEnumerable<T>
而不是例如List<T>
或 T[]
以便您可以重新分配它(例如 targetHandles = targetHandles.Where(x => pred(x));
【参考方案1】:
我不确定您的问题是仅在 LINQ 的 Where
中,还是在 where T: DtoBase
中,所以我将两者都讨论
在泛型方法中的位置
public static void Merge<T>(ObservableCollection<T> target,
ObservableCollection<T> source)
where T : DtoBase
方法Merget<T>
的标识符后面的<T>
,告诉我们这是一个generic method
,也就是说你可以用各种类调用它,只要类满足where
条件:
where T : DtoBase
所以你可以用任何类 T 调用这个方法,只要这个类是(派生自)DtoBase
。
例子:
class DtoBase ...;
class Derived : DtoBase ...;
ObservableCollection<DtoBase> myBases = ...
ObservableCollection<DtoBase> yourBases = ...
ObservableCollection<Derived> myDeriveds = ...
ObservableCollection<Derived> yourDeriveds = ...
现在您可以调用方法Merge
,并将T
之外的所有出现替换为与where T : DtoBase
匹配的任何内容。所以以下都是有效的:
Merge(myBases, yourBases);
Merge(myBases, myBases);
Merge(myDeriveds, yourDeriveds);
Merge(myBases, myDeriveds);
以下内容无法编译:
Merge(myBases, 4); // integer 4 is not a DtoBase
Merge("Hello", myBases); // string is not a DtoBase
在 LINQ 语句中的位置
var targetHandles = target.Where(t => true);
该语句中的t
与泛型方法中的T无关。
如果您查看Enumerable.Where 的定义,您会发现它是一个通用扩展方法。
public static IEnumerable<TSource> Where<TSource> (
this IEnumerable<TSource> source,
Func<TSource,bool> predicate);
上面讨论了泛型:Where 有一个类型 TSource
,与我们之前讨论的 T
相当:无论您在哪里看到 TSource
,都应该将其替换为相同的实际类型。
我们还在第一个参数前面看到了关键字this
。这意味着它是一个扩展方法:你可以把第一个参数放在方法的前面,像这样:
IEnumerable<Product> products = ...
IEnumerable<Product> cheapProducts = products.Where(product => product.Price < 1);
这与:
IEnumerable<Product> cheapProducts = Enumerable<Product>.Where(products,
product => product.Price < 1);
所以扩展方法只是一些花哨的语法糖。它使它看起来好像是 Product 的一种方法。
现在是有趣的部分:product => product.Price < 1
中的 product
。 product 只是一个名称选择得当的标识符。我也可以写:
.Where(t => t.Price < 1);
t => t.Price < 1
称为 lambda 表达式。它是函数定义的一些简写:我们创建一个带有输入参数t
的方法,它返回t.price < 1
。 t
的类型可以在泛型中找到:
public static IEnumerable<TSource> Where<TSource> (
this IEnumerable<TSource> source,
Func<TSource,bool> predicate);
在我的示例中,TSource
是 Product
,因此第一个参数应该是 IEnumerable<Product>
。返回值也是IEnumerable<TSource>
。
IEnumerable<Product> products = ...
IEnumerable<Product> cheapProducts = products.Where(...);
predicate
是Func<TSource, bool>
。这意味着:任何带有输入 TSource(在我们的例子中是 Product)的函数都返回一个布尔值。所以如果你在某个地方看到:
Func<int, string, DateTime, Point>
那么它的意思是:任何方法,具有int、string、DateTime三个类型的输入参数(按此顺序),它返回一个Point:
Point MyFunct(int i, string txt, DateTime date) ...
我们已经看到 lambda 表达式t => t.Price < 1
是一个将Product t
作为输入参数并返回t.Price < 1
的值的方法,这显然是一个布尔值。因此,这个 lambda 表达式匹配 Func
IEnumerable<Product> cheapProducts = products.Where(product => product.Price < 1);
那么这有什么作用呢?它从products
的序列中取出每个product
,并将其放入函数product => product.Price < 1
。换句话说:从 Products 序列中的每个产品,它计算布尔值product.Price < 1
。是真的,那么我们就保留它,如果不是,我们就不按照Where
返回的顺序使用它。
效果是,Where(product => product.Price < 1)
返回所有 Products 的序列,其属性 Price 的值小于 1。
回到你的问题
您的Where
是一个相当奇怪的人。
IEnumerable<Target> targets = ...
targets.Where(t => true);
我们知道targets
是一个Target 类型的序列。因此,我们知道谓词是一种将 Target 作为输入并返回 bool 的方法:
布尔谓词(目标t)返回真
嗯,它确实返回一个布尔值。事实上,它总是返回相同的布尔值,它甚至不看输入参数 t,它总是返回 true。
Where(t => true)
类似于以下内容:
foreach(Target target in targets)
if (true) return target;
好吧,这没什么用:它返回完整的输入序列:
IEnumerable<Target> targets = ...
var result = targets.Where(t => true);
我们知道result
与targets
具有相同的元素,顺序相同。该语句不是很有用。
结束语
当您必须在 LINQ 语句中定义自己的标识符时,请尝试使用复数名词来标识序列,使用单数名词来标识序列的元素。尽量避免使用无意义的标识符 t =>
。
输入更少的字符并不是选择错误标识符的好借口!
IEnumerable<Customer> customers = ...
IEnumerable<Order> orders = ...
var customersWithTheirOrders = customer.GroupJoin(orders,
customer => customer.Id, // from every Customer take the primary key
order => order.CustomerId, // from every Order take the foreign key
(customer, ordersOfThisCustomer) => new
Id = customer.Id,
Name = customer.Name,
Address = customer.Address,
Orders = ordersOfThisCustomer.Select(order => new
Date = order.Date,
Total = order.Total,
)
.ToList(),
)
这是一个非常大的 LINQ。 GroupJoin
你可能不熟悉,但是因为我用了复数和单数名词,所以读起来并不难:
简而言之:我们有一系列客户和一系列订单。我们集团加入客户和订单。这意味着,我们从每个客户那里获取 ID,从每个订单中获取 CustomerId。我们试图匹配它们。从每个有零个或多个订单的客户中,我们制作一个新对象。
这个新对象包含客户的 ID,以及他的姓名和地址。从该客户的订单序列中的每个订单中,我们创建一个新对象。这个新对象包含订单的日期和总计。
因为我使用了复数和单数名词,所以这个文字描述非常符合LINQ。您不必想知道t
和x
是什么。 Customer
和 OrdersOfThisCustomer
更容易理解它们的含义。
【讨论】:
【参考方案2】:表达式source.Where(x => true)
将返回一个IEnumerable
,其中包含所有 source
中的项目,无论每个项目的值如何。这是因为true
是确定应该过滤哪些项目的整个谓词,而true
始终是true
。
在大多数情况下,这样的谓词是项目的函数(例如x > 3
),但不一定是。
因此,target.Where(t => true)
返回一个 IEnumerable<T>
,其中包含 所有 target
中的项目。这样,可以创建一个单独的集合,可以单独使用(不影响target
)。
另一方面,var targetHandles = target;
会创建对target
的引用。
【讨论】:
IEnumerable<T>
不是一个集合——它是一个返回集合成员的枚举。根据您所说的“单独使用”的含义,当您从枚举中修改返回的 T
时,您可能确实在修改 target
的成员。
我的立场是正确的;感谢您的澄清。以上是关于Linq 在哪里有奇怪的情况的主要内容,如果未能解决你的问题,请参考以下文章