为什么LINQ FirstOrDefault或First生成的调用数

时间:2015-01-20 19:32:20

标签: c# .net linq

我注意到我们的应用程序出现了一些性能问题,并将其跟踪到我们课程中对ID属性的大量调用。

我已经设置了一个样本来解释。

我们有两个类,Person和Address。

我创建了10个实例,每个实例都有ID字段(Person_Id,Address_Id)。

在此示例的情况下,Person_Id为1映射到Address_Id为1。

为了将这些链接在一起,我在Person的'Address'中有一个只读属性,它通过对地址集合执行LINQ查询来返回相关的地址对象。 为简单起见,我将返回Address_Id = Person_Id的地址,因为我在每个列表中都有相同数量的项目,这是用于测试。

public Address Address
{
    get
    {
        return Addresses.FirstOrDefault(a => a.Address_Id == Person_Id);
    }
}

Person_Id是具有私有支持字段的公共属性。非常简单。

private int _person_Id;
public int Person_Id 
{
    get
    {
        return _person_Id;
    }
    set
    {
        _person_Id = value;
    }
}

当跟踪调用Person_Id内部的次数时,该数量始终高于人员记录的数量。在这种情况下,我正在迭代人员记录列表并输出人员的姓名和状态。

foreach (var person in persons)
{
   var name = person.Name;
   var state = person.Address.State;
   Console.WriteLine(name + "\t" + state);
}

以下是根据迭代的人员实体数量,呼叫数量如何分解:

calls based on # of records

回顾一下数学,我们可以看到为我们所在的当前实体添加了#个地址调用,并且 <>>增加了对Person_Id的总调用次数。例如:如果我们迭代了5个人的记录,则有5个人调用get的'Address'属性,15个调用get的'Person_Id'属性。 15是(5 + 4 + 3 + 2 + 1),是对“地址”的调用的总和。

我很好奇这些数字来自哪里。 FirstOrDefault 查找的情况相同。如果我使用 Single ,则呼叫要高得多。

如果我改为创建一个像这样的局部变量:

int personId = Person_Id;

然后在LINQ查询中使用它:

return Addresses.Find(a => a.Address_Id == personId);

然后调用是1比1 - 我有一次调用Address和Person_Id,正如我在LINQ查询中所期望的那样。

有谁知道为什么这些电话会以这种方式夸大?在进行优化的过程中,我有兴趣了解更多信息。

由于

2 个答案:

答案 0 :(得分:7)

您基本上对Addresses中的每个地址说'#34;,评估此谓词,直到谓词返回true,此时返回该地址。&#34; < / p>

谓词是lambda表达式,它使用Person_Id属性,因此每次都必须对其进行评估。

或者换句话说,假设您使用普通方法而不是lambda表达式来创建谓词:

public Address Address
{
    get
    {
        Predicate<Address> predicate = new Predicate<Address>(AddressIdMatches);
        return Addresses.FirstOrDefault(predicate);
    }
}

private boolean AddressIdMatches(Address a)
{
    return a.Address_Id == Person_Id;
}

更清楚了吗?每个地址都会调用一次该方法,并且很明显,每次调用该方法时,它都会评估Person_Id。这就是当您使用lambda表达式时,编译器基本上为您构建的内容。

答案 1 :(得分:0)

在问题的上下文中,这个lambda表达式:

a => a.Address_Id == Person_Id

真的等同于这个lambda表达式:

a => a.Address_Id == this.Person_Id

您隐含地在lambda中捕获this - 而不是this.Person_Id的值。因此,对于已检查的每个Person_Id Address,会重复调用a属性getter。

如果您将代码更改为:

int personId = Person_Id;
...
a => a.Address_Id == personId

然后您将捕获变量personId,每次读取时都不需要调用Person_Id属性getter。

相关问题