创建LINQ查询内联委托实例的效率?

时间:2011-03-01 22:31:36

标签: c# .net linq optimization clr

以下两个例子以不同的方式做同样的事情。我正在比较它们。

版本1

为了举例,定义任何基于业务逻辑从ExpandoObject创建和返回XElement的方法:

var ToExpando = new Func<XElement, ExpandoObject>(xClient =>
{
    dynamic o = new ExpandoObject();    
    o.OnlineDetails = new ExpandoObject();
    o.OnlineDetails.Password = xClient.Element(XKey.onlineDetails).Element(XKey.password).Value;
    o.OnlineDetails.Roles = xClient.Element(XKey.onlineDetails).Element(XKey.roles).Elements(XKey.roleId).Select(xroleid => xroleid.Value);
    // More fields TBD.
}

从LINQ to XML查询调用上面的委托:

var qClients =
    from client in xdoc.Root.Element(XKey.clients).Elements(XKey.client)
    select ToExpando(client);

第2版

在LINQ查询中完成所有操作,包括创建和调用Func委托。

var qClients =
from client in xdoc.Root.Element(XKey.clients).Elements(XKey.client)
select (new Func<ExpandoObject>(() =>
        {
            dynamic o = new ExpandoObject();
            o.OnlineDetails = new ExpandoObject();
            o.OnlineDetails.Password = client.Element(XKey.onlineDetails).Element(XKey.password).Value;
            o.OnlineDetails.Roles = client.Element(XKey.onlineDetails).Element(XKey.roles).Elements(XKey.roleId).Select(xroleid => xroleid.Value);
            // More fields TBD.
  return o;
  }))();

考虑委托创建是在select部分,版本2 效率低吗?它是由C#编译器还是运行时管理或优化的,所以它无关紧要?

我喜欢版本2的紧密性(在查询中保留对象创建逻辑),但我知道它可能不可行,具体取决于编译器或运行时的功能。

2 个答案:

答案 0 :(得分:4)

后一种方法对我来说非常可怕。我相信,每当你每次捕获一个不同的客户时,都必须真正创建一个新的代表,但我个人不会这样做。鉴于你在那里有真实的陈述,为什么不写一个普通的方法?

private static ToExpando(XElement client)
{
    // Possibly use an object initializer instead?
    dynamic o = new ExpandoObject();    
    o.OnlineDetails = new ExpandoObject();
    o.OnlineDetails.Password = client.Element(XKey.onlineDetails)
                                     .Element(XKey.password).Value;
    o.OnlineDetails.Roles = client.Element(XKey.onlineDetails)
                                  .Element(XKey.roles)
                                  .Elements(XKey.roleId)
                                  .Select(xroleid => xroleid.Value);
    return o;
}

然后使用以下命令查询:

var qClients = xdoc.Root.Element(XKey.clients)
                        .Elements(XKey.client)
                        .Select(ToExpando);

我会更多地关注代码的可读性而不是创建委托的性能,这通常很快。我认为没有必要像你现在看起来那样使用几乎一样多的lambdas。考虑一下你在一年后回到这段代码的时间。你真的会发现嵌套的lambda比方法更容易理解吗?

(顺便说一句,将转换逻辑分离成一个方法可以很容易地单独测试......)

编辑:即使你想要在LINQ表达式中完成所有操作,为什么你如此热衷于创建另一个间接层?只是因为查询表达式不允许语句lambdas?鉴于你只做一个简单的选择,这很容易应付:

var qClients = xdoc.Root
           .Element(XKey.clients)
           .Elements(XKey.client)
           .Select(client => {
               dynamic o = new ExpandoObject();
               o.OnlineDetails = new ExpandoObject();
               o.OnlineDetails.Password = client.Element(XKey.onlineDetails)
                                                .Element(XKey.password).Value;
               o.OnlineDetails.Roles = client.Element(XKey.onlineDetails)
                                             .Element(XKey.roles)
                                             .Elements(XKey.roleId)
                                             .Select(xroleid => xroleid.Value);
               return o; 
           });

答案 1 :(得分:2)

确实,您的第二个版本会重复创建新的Func实例 - 但是,这只是意味着分配一些小对象(闭包)并使用指向函数的指针。与您需要在委托主体中执行的动态查找相比,我认为这不是一个很大的开销(使用dynamic个对象)。

或者,你可以声明一个像这样的本地lambda函数:

Func<XElement, ExpandoObject> convert = client => {
    dynamic o = new ExpandoObject();
    o.OnlineDetails = new ExpandoObject();
    o.OnlineDetails.Password = 
       client.Element(XKey.onlineDetails).Element(XKey.password).Value;
    o.OnlineDetails.Roles = client.Element(XKey.onlineDetails).
       Element(XKey.roles).Elements(XKey.roleId).
       Select(xroleid => xroleid.Value);
    // More fields TBD.
    return o;
}

var qClients =
    from client in xdoc.Root.Element(XKey.clients).Elements(XKey.client)
    select convert(client);

这样,您只需创建一个委托,但将执行转换的代码保持在接近实现查询的代码的位置。

另一种选择是使用匿名类型 - 在您的方案中使用ExpandoObject的原因是什么?匿名类型的唯一限制是您可能无法从其他程序集访问它们(它们是internal),但使用dynamic使用它们应该没问题......

您的选择可能如下所示:

select new { OnlineDetails = new { Password = ..., Roles = ... }}

最后,您还可以使用Reflection将匿名类型转换为ExpandoObject,但这可能效率更低(即很难有效编写)