【问题标题】:Efficiency of creating delegate instance inline of LINQ query?创建 LINQ 查询的委托实例内联的效率?
【发布时间】:2011-07-06 21:10:35
【问题描述】:

以下是两个以不同方式做同一件事的示例。我在比较它们。

版本 1

举个例子,定义任何方法来根据业务逻辑从XElement 创建和返回ExpandoObject

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 的紧凑性(将对象创建逻辑保留在查询中),但我知道它可能不可行,具体取决于编译器或运行时的功能。

【问题讨论】:

  • 为什么是动态?似乎o 的类型在编译时就知道了...
  • @Billy ONeal:据我所知,如果不使用 dynamic,您将无法展开 ExpandoObject。
  • 哎呀——我以为 ExpandoObject 是你定义的类型。

标签: c# .net linq optimization clr


【解决方案1】:

确实,您的第二个版本会重复创建新的 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 = ... }}

最后,你还可以使用反射将匿名类型转换为ExpandoObject,但这可能会更加低效(即很难高效编写)

【讨论】:

  • P:关于我为什么不使用匿名类型的好问题。这是因为我正在使用调用代码跨越程序集边界。我的问题省略了那部分,也没有表明给定的代码包含在 IEnumerable 类型的方法中。 stackoverflow.com/questions/2993200
【解决方案2】:

后一种方法对我来说看起来很可怕。我相信每次你要捕获不同的客户时,它都必须真正创建一个新的委托,但我个人根本不会那样做。既然里面有真实的语句,为什么不写一个普通的方法呢?

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);

我会更多关注代码的可读性,而不是创建委托的性能,这通常很快。我认为没有必要像您目前似乎热衷的那样使用尽可能多的 lambda。想想一年后你什么时候回到这段代码。你真的会发现嵌套的 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; 
           });

【讨论】:

  • 你能解释一下为什么delegate被创建了几次,我无法理解吗?
  • @Andrey:翻译成Select(new Func&lt;..&gt;(client =&gt; (new Func&lt;..&gt;(...)()))
  • @Andrey:在原始代码中,创建了一个委托以传递给 Select... 但该委托创建了 另一个 委托,并执行它以返回对象。对我来说似乎毫无意义。
  • @Tomas Petricek 谢谢!我现在明白了!使用旧的(非 sql-like)语法委托创建可以很容易地避免,但 sql-like 语法不允许多行 lambdas。
  • @Jon Skeet 对我来说这似乎是在滥用类似 sql 的语法。如果您用实际的代码代理替换对ToExpando 的引用,则只会创建一次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多