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&lt;T&gt; 而不是例如List&lt;T&gt;T[] 以便您可以重新分配它(例如 targetHandles = targetHandles.Where(x =&gt; pred(x)); 【参考方案1】:

我不确定您的问题是仅在 LINQ 的 Where 中,还是在 where T: DtoBase 中,所以我将两者都讨论

在泛型方法中的位置

public static void Merge<T>(ObservableCollection<T> target,
                            ObservableCollection<T> source)
                            where T : DtoBase

方法Merget&lt;T&gt;的标识符后面的&lt;T&gt;,告诉我们这是一个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 =&gt; product.Price &lt; 1 中的 product。 product 只是一个名称选择得当的标识符。我也可以写:

.Where(t => t.Price < 1);

t =&gt; t.Price &lt; 1 称为 lambda 表达式。它是函数定义的一些简写:我们创建一个带有输入参数t 的方法,它返回t.price &lt; 1t 的类型可以在泛型中找到:

public static IEnumerable<TSource> Where<TSource> (
    this IEnumerable<TSource> source,
    Func<TSource,bool> predicate);

在我的示例中,TSourceProduct,因此第一个参数应该是 IEnumerable&lt;Product&gt;。返回值也是IEnumerable&lt;TSource&gt;

IEnumerable<Product> products = ...
IEnumerable<Product> cheapProducts = products.Where(...);

predicateFunc&lt;TSource, bool&gt;。这意味着:任何带有输入 TSource(在我们的例子中是 Product)的函数都返回一个布尔值。所以如果你在某个地方看到:

Func<int, string, DateTime, Point>

那么它的意思是:任何方法,具有int、string、DateTime三个类型的输入参数(按此顺序),它返回一个Point:

Point MyFunct(int i, string txt, DateTime date) ...

我们已经看到 lambda 表达式t =&gt; t.Price &lt; 1 是一个将Product t 作为输入参数并返回t.Price &lt; 1 的值的方法,这显然是一个布尔值。因此,这个 lambda 表达式匹配 Func

IEnumerable<Product> cheapProducts = products.Where(product => product.Price < 1);

那么这有什么作用呢?它从products 的序列中取出每个product,并将其放入函数product =&gt; product.Price &lt; 1。换句话说:从 Products 序列中的每个产品,它计算布尔值product.Price &lt; 1。是真的,那么我们就保留它,如果不是,我们就不按照Where返回的顺序使用它。

效果是,Where(product =&gt; product.Price &lt; 1) 返回所有 Products 的序列,其属性 Price 的值小于 1。

回到你的问题

您的Where 是一个相当奇怪的人。

IEnumerable<Target> targets = ...
targets.Where(t => true);

我们知道targets 是一个Target 类型的序列。因此,我们知道谓词是一种将 Target 作为输入并返回 bool 的方法:

布尔谓词(目标t)返回真

嗯,它确实返回一个布尔值。事实上,它总是返回相同的布尔值,它甚至不看输入参数 t,它总是返回 true。

Where(t =&gt; true) 类似于以下内容:

foreach(Target target in targets)

    if (true) return target;

好吧,这没什么用:它返回完整的输入序列:

IEnumerable<Target> targets = ...
var result = targets.Where(t => true);

我们知道resulttargets 具有相同的元素,顺序相同。该语句不是很有用。

结束语

当您必须在 LINQ 语句中定义自己的标识符时,请尝试使用复数名词来标识序列,使用单数名词来标识序列的元素。尽量避免使用无意义的标识符 t =&gt;

输入更少的字符并不是选择错误标识符的好借口!

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。您不必想知道tx 是什么。 CustomerOrdersOfThisCustomer 更容易理解它们的含义。

【讨论】:

【参考方案2】:

表达式source.Where(x =&gt; true) 将返回一个IEnumerable,其中包含所有 source 中的项目,无论每个项目的值如何。这是因为true 是确定应该过滤哪些项目的整个谓词,而true 始终是true

在大多数情况下,这样的谓词是项目的函数(例如x &gt; 3),但不一定是。

因此,target.Where(t =&gt; true) 返回一个 IEnumerable&lt;T&gt;,其中包含 所有 target 中的项目。这样,可以创建一个单独的集合,可以单独使用(不影响target)。

另一方面,var targetHandles = target; 会创建对target 的引用。

【讨论】:

IEnumerable&lt;T&gt; 不是一个集合——它是一个返回集合成员的枚举。根据您所说的“单独使用”的含义,当您从枚举中修改返回的 T 时,您可能确实在修改 target 的成员。 我的立场是正确的;感谢您的澄清。

以上是关于Linq 在哪里有奇怪的情况的主要内容,如果未能解决你的问题,请参考以下文章

LINQ更新:找不到行或行已更改

使用Linq的情况

.NET 2.0 运行时上的 LINQ

可以在不删除“使用”的情况下从 Intellisense 隐藏 Linq 和其他扩展吗?

页面右侧有奇怪的小白间距

子弹物理质心和奇怪的物体反应