将方法传递给LINQ查询 [英] Passing a method to a LINQ query

查看:76
本文介绍了将方法传递给LINQ查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我目前正在从事的项目中,我们有许多静态表达式,当我们在它们上调用Invoke方法并将lambda表达式的参数传递给它们时,必须使用变量将它们引入局部范围.

In a project I'm currently working on, we have many static Expressions that we have to bring in local scope with a variable when we call the Invoke method on them and pass our lambda expressions' arguments to.

今天,我们声明了一个静态方法,该方法的参数正是查询所期望的类型.因此,我和我的同事正在四处寻找是否可以在查询的Select语句中使用此方法来执行项目,而不是在不将其引入本地范围的情况下在整个对象上调用它.

Today, we declared a static method whose parameter is exactly the type that the query is expecting. So, my coworker and I were messing around to see if we could get this method to do the project in the Select statement of our query, instead of invoking it on the whole object, without bringing it into local scope.

它奏效了!但是我们不明白为什么.

And it worked! But we do not understand why.

想象这样的代码

// old way
public static class ManyExpressions {
   public static Expression<Func<SomeDataType, bool> UsefulExpression {
      get {
         // TODO implement more believable lies and logic here
         return (sdt) => sdt.someCondition == true && false || true; 
      }
   }
}

public class ARealController : BaseController {

   /* many declarations of important things */

   public ARealClass( /* many ninjected in things */) {
      /* many assignments */
   }

   public JsonNet<ImportantDataResult> getSomeInfo(/* many useful parameter */) {

      var usefulExpression = ManyExpressions.UsefulExpression;

      // the db context is all taken care of in BaseController
      var result = db.SomeDataType
         .Where(sdt => usefulExpression.Invoke(sdt))
         .Select(sdt => new { /* grab important things*/ })
         .ToList();

      return JsonNet(result);
   }
}

然后您就可以做到这一点!

And then you get to do this!

// new way
public class SomeModelClass {

   /* many properties, no constructor, and very few useful methods */
   // TODO come up with better fake names
   public static SomeModelClass FromDbEntity(DbEntity dbEntity) {
      return new SomeModelClass { /* init all properties here*/ };
   }
}

public class ARealController : BaseController {

   /* many declarations of important things */

   public ARealClass( /* many ninjected in things */) {
      /* many assignments */
   }

   public JsonNet<SomeModelClass> getSomeInfo(/* many useful parameter */) {

      // the db context is all taken care of in BaseController
      var result = db.SomeDataType
         .Select(SomeModelClass.FromDbEntity) // TODO; explain this magic
         .ToList();

      return JsonNet(result);
   }
}

因此,当ReSharper提示我执行此操作时(这并不常见,因为与委托所期望的类型相匹配的条件并不经常得到满足),它表示转换为方法组.我有点模糊地理解方法组是一组方法,而C#编译器可以负责将方法组转换为LINQ提供程序的显式类型化且适当的重载,但不是...但是我很模糊为什么这可以正常工作.

So when ReSharper prompts me to do this (which is not often, as this condition of matching the type that is expected by the Delegate isn't often satisfied), it says convert to a Method Group. I kind of vaguely understand that a Method Group is a set of methods, and the C# compiler can take care of converting the method group to an explicitly typed and appropriate overload for the LINQ provider and what not... but I'm fuzzy on why this works exactly.

这是怎么回事?

推荐答案

当您不了解某事时,问一个问题真是太好了,但问题是,很难知道某人不了解哪一点.我希望我能在这里帮助您,而不是告诉您一堆您知道的东西,而实际上没有回答您的问题.

It's great to ask a question when you don't understand something, but the problem is that it can be hard to know which bit someone doesn't understand. I hope I help here, rather than tell you a bunch of stuff you know, and not actually answer your question.

让我们回到Linq之前,表达式之前,lambda之前,甚至是匿名代表之前的日子.

Let's go back to the days before Linq, before expressions, before lambda, and before even anonymous delegates.

在.NET 1.0中,我们没有任何一个.我们甚至没有泛型.我们确实有代表.委托与函数指针(如果您知道C,C ++或具有此类的语言)或函数作为参数/变量(如果您知道Javascript或具有此类的语言)相关.

In .NET 1.0 we didn't have any of those. We didn't even have generics. We did though have delegates. And a delegate is related to a function pointer (if you know C, C++ or languages with such) or function as argument/variable (if you know Javascript or languages with such).

我们可以定义一个委托人

We could define a delegate:

public delegate int MyDelegate(double someValue, double someOtherValue);

然后将其用作字段,属性,变量,方法参数的类型或事件的基础.

And then use it as a type for a field, property, variable, method argument or as the basis of an event.

但是当时唯一为委托提供值的唯一方法是引用实际方法.

But at the time the only way to actually give a value for a delegate was to refer to an actual method.

public int CompareDoubles(double x, double y)
{
  if (x < y) return -1;
  return y < x ? 1 : 0;
}

MyDelegate dele = CompareDoubles;

我们可以使用dele.Invoke(1.0, 2.0)或简称dele(1.0, 2.0)来调用它.

We can invoke that with dele.Invoke(1.0, 2.0) or the shorthand dele(1.0, 2.0).

现在,由于我们在.NET中有重载,因此我们可以拥有CompareDoubles所引用的多个内容.这不是问题,因为如果我们也有public int CompareDoubles(double x, double y, double z){…}编译器可能知道您可能只打算将另一个CompareDoubles分配给dele,所以它是明确的.尽管如此,尽管在上下文中CompareDoubles表示一个方法,该方法接受两个double参数并返回一个int,但在该上下文之外CompareDoubles表示所有具有该名称的方法的组.

Now, because we have overloading in .NET, we can have more than one thing that CompareDoubles refers to. That isn't a problem, because if we also had e.g. public int CompareDoubles(double x, double y, double z){…} the compiler could know that you could only possibly have meant to assign the other CompareDoubles to dele so it's unambiguous. Still, while in the context CompareDoubles means a method that takes two double arguments and returns an int, outside of that context CompareDoubles means the group of all the methods with that name.

方法组.

现在,在.NET 2.0中,我们获得了泛型,这对于委托很有用,同时在C#2中,我们获得了匿名方法,这也很有用.从2.0版开始,我们现在可以做到:

Now, with .NET 2.0 we got generics, which is useful with delegates, and at the same time in C#2 we got anonymous methods, which is also useful. As of 2.0 we could now do:

MyDelegate dele = delegate (double x, double y)
{
  if (x < y) return -1;
  return y < x ? 1 : 0;
};

这部分只是C#2中的语法糖,在幕后仍然有一个方法,尽管它有一个无法解释的名称"(一个名称可以用作.NET名称,但不能用作C#名称) ,因此C#名称不能与此冲突).如果像通常那样创建一个方法,让它们仅与特定的委托一起使用一次,这是很方便的.

This part was just syntactic sugar from C#2, and behind the scenes there's still a method there, though it has an "unspeakable name" (a name that is valid as a .NET name but not valid as a C# name, so C# names can't clash with it). It was handy if, as was often the case, one was creating methods just to have them used once with a particular delegate though.

进一步向前移动,在.NET 3.5中,FuncAction委托具有协方差和逆方差(对于委托来说是很棒的)(对于基于类型重用同一名称,而不是一堆不同的名字,很棒)代表(通常非常相似)),随之而来的是带有Lambda表达式的C#3.

Move forward a bit further, and at .NET 3.5 have covariance and contravariance (great with delegates) the Func and Action delegates (great for reusing the same name based on type, rather than having a bunch of different delegates which were often very similar) and along with it came C#3 which had lambda expressions.

现在,这些在某种用途中有点像匿名方法,但在另一种用途中却没有.

Now, these are a bit like anonymous methods in one use, but not in another.

这就是为什么我们不能这样做:

That's why we can't do:

var func = (int i) => i * 2;

var从分配给它的含义中找出含义,但是lamda从分配给它们的内容中得出含义,所以这是模棱两可的.

var works out what it means from what's been assigned to it, but lamdas work out what they are from what they've been assigned to, so this is ambiguous.

这可能意味着:

Func<int, int> func = i => i * 2;

在这种情况下,它是简写:

In which case it's shorthand for:

Func<int, int> func = delegate(int i){return i * 2;};

反过来就是这样的简写:

Which in turn is shorthand something like for:

int <>SomeNameImpossibleInC# (int i)
{
  return i * 2;
}
Func<int, int> func = <>SomeNameImpossibleInC#;

但它也可以用作:

Expression<Func<int, int>> func = i => i * 2;

以下简称:

Expression<Func<int, int>> func = Expression.Lambda<Func<int, int>>(
  Expression.Multiply(
    param,
    Expression.Constant(2)
  ),
  param
);

我们在.NET 3.5中也使用了Linq,它们大量使用了这两者.实际上,表达式被视为Linq的一部分,并且位于System.Linq.Expressions命名空间中.请注意,这里得到的对象是对要完成的操作的描述(采用参数,将其乘以2,得到结果),而不是如何执行的操作.

And we also with .NET 3.5 have Linq which makes heavy use of both of these. Indeed, Expressions is considered part of Linq and is in the System.Linq.Expressions namespace. Note that the object we get here is a description of what we want done (take the parameter, multiply it by two, give us the result) not of how to do it.

现在,Linq有两种主要操作方式.在IQueryableIQueryable<T>以及在IEnumerableIEnumerable<T>上.前者定义了要在提供者"上使用的操作,而提供者所做的"取决于该提供者,而后者则定义了在内存中的值序列上的相同操作.

Now, Linq operates in two main ways. On IQueryable and IQueryable<T> and on IEnumerable and IEnumerable<T>. The former defines operations to be used on "a provider" with just what "a provider does" being up to that provider, and the latter defines the same operations on in-memory sequences of values.

我们可以从一个转移到另一个.我们可以使用AsQueryableIEnumerable<T>转换为IQueryable<T>,这将为我们提供该可枚举的包装,并且只需将IQueryable<T>视为一个即可将其转换为IEnumerable<T>,因为IQueryable<T>源自IEnumerable<T>.

We can move from one to the other. We can turn an IEnumerable<T> into an IQueryable<T> with AsQueryable which will give us a wrapper on that enumerable, and we can turn the IQueryable<T> into an IEnumerable<T> just by treating it as one, because IQueryable<T> derives from IEnumerable<T>.

可枚举形式使用委托. Select工作方式的简化版本(该版本有很多优化措施,我跳过了错误检查和间接检查以确保立即进行错误检查)是:

The enumerable form uses the delegates. A simplified version of how Select works (there are many optimisations this version leaves out, and I'm skipping error checking and in indirection to ensure that error checking happens immediately) would be:

public static IEnumerable<TResult> Select(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
  foreach(TSource item in source) yield return selector(item);
}

另一方面,可查询版本的工作方式是从Expression<TSource, TResult>获取表达式树,使其成为包含对Select的调用和可查询源的表达式的一部分,并返回包装该表达式的对象.因此,换句话说,对queryable的Select的调用将返回一个对象,该对象表示对queryable的Select的调用!

The queryable version on the other hand works by taking the expression tree from the Expression<TSource, TResult> making it part of an expression that includes the call to Select, and the source queryable, and returns an object wrapping that expression. So in other words a call to queryable's Select returns an object that represents a call to queryable's Select!

该怎么做取决于提供者.数据库提供程序将它们转换为SQL,可枚举对象在表达式上调用Compile()创建委托,然后回到上面的Select的第一个版本,依此类推.

Just what is done with that depends on the provider. Database providers turn them into SQL, enumerables call Compile() on the expression to create a delegate and then we're back at the first version of Select above, and so on.

但是考虑到历史,让我们再次回顾历史.一个lambda可以代表一个表达式或一个委托(如果是一个表达式,我们可以Compile()来获得同一个委托).委托是通过变量指向方法的一种方法,而方法是方法组的一部分.所有这些都是基于技术的,在第一个版本中,只能通过创建方法然后将其传递来调用它.

But that history considered, let's go backwards through the history again. A lambda can represent either an expression or a delegate (and if an expression, we can Compile() it to get the same delegate). A delegate is a way of pointing to a method through a variable, and a method is part of a method group. All of this is built on technology which in the first version could only be called by creating a method and then passing that.

现在,可以说我们有一个方法,它带有一个参数,并有一个结果.

Now, lets say we have a method that takes a single argument and has a result.

public string IntString(int num) { return num.ToString(); }

现在可以说我们在lambda选择器中引用了它:

Now lets say we referenced it in a lambda selector:

Enumerable.Range(0, 10).Select(i => IntString(i));

我们有一个lambda为委托创建一个匿名方法,而该匿名方法又调用了具有相同参数和返回类型的方法.在某种程度上类似于我们是否拥有:

We have a lambda creating an anonymous method for a delegate, and that anonymous method in turn calls a method with the same argument and return types. In a way that's a bit like if we had:

public string MyAnonymousMethod(int i){return IntString(i);}

MyAnonymousMethod在这里有点毫无意义;它所做的只是调用IntString(i)并返回结果,所以为什么不首先调用IntString并切穿该方法:

MyAnonymousMethod is a bit pointless here; all it does is call IntString(i) and return the result, so why not just call IntString in the first place and cut out going through that method:

Enumerable.Range(0, 10).Select(IntString);

通过采用基于lambda的委托并将其转换为方法组,我们已经消除了不必要的(尽管请参见下面有关委托缓存的注释)级别.因此,ReSharper的建议是转换为方法组",或者它的措辞是这样(我自己不使用ReSharper).

We've cut out a needless (though see note below about delegate caching) level of indirection by taking the lambda-based delegate and converting it to a method group. Hence ReSharper's advice "Convert to Method Group" or however it's worded (I don't use ReSharper myself).

尽管这里有一些注意事项. IQueryable<T>的Select仅接受表达式,因此提供程序可以尝试找出如何将其转换为其处理方式(例如,针对数据库的SQL). IEnumerable<T>的Select仅接受委托,因此可以在.NET应用程序本身中执行委托.我们可以使用Compile()从前者转到后者(当queryable实际上是包装的枚举时),但是我们不能从后者转到前者:我们没有办法接受委托并转向它变成一个表达式,表示除调用此委托人"之外的任何内容,这不能转化为SQL.

There is though something to be careful of here. IQueryable<T>'s Select only takes expressions, so the provider can try to work out how to convert it to its way of doing stuff (e.g. SQL against a database). IEnumerable<T>'s Select only takes delegates so they can be executed in the .NET application itself. We can go from the former to the latter (when the queryable is really a wrapped enumerable) with Compile(), but we can't go from the latter to the former: We don't have a way of taking a delegate and turning it into an expression that means anything other than "call this delegate" which isn't something that can be turned into SQL.

现在,当我们使用像i => i * 2这样的lambda表达式时,由于解析规则使该表达式具有可查询性(它可以作为类型,处理这两种情况,但表达式形式适用于大多数派生类型).如果尽管我们显式地为其指定了委托,无论是因为我们在Func<>处将其键入还是来自方法组,那么采用表达式的重载都不可用,而采用采用委托的表达式.这意味着它不会传递到数据库,而是直到此时的linq表达式都将成为数据库部分",它会被调用,其余的工作将在内存中完成.

Now when we use a lambda expression like i => i * 2 it will be an expression when used with IQueryable<T> and a delegate when used with IEnumerable<T> due to overload resolution rules favouring the expression with queryable (as a type it can handle both, but the expression form works with the most derived type). If though we explicitly give it a delegate, whether because we typed it somewhere as Func<> or it comes from a method group, then the overloads taking expressions aren't available and those taking delegates are used. This means it doesn't get passed to the database but rather the linq expression up to that point becomes the "database part" and it gets called and the rest of the work done in memory.

最好避免95%的时间.因此,在95%的时间里,如果通过数据库支持的查询获得转换为方法组"的建议,您应该认为嗯!那实际上是委托.为什么是委托?我可以将其更改为表达式吗? ".您只有剩下的5%的时间应该认为如果我只输入方法名称,那会短一些". (另外,使用方法组代替委托可以防止编译器可以否则委托的方式进行缓存,因此效率可能较低.)

95% of the time that's best avoided. So 95% of the time if you get advice of "convert to method group" with a database-backed query you should think "uh oh! that's actually a delegate. Why is that a delegate? Can I change it to be an expression?". Only the remaining 5% of the time should you think "that'll be slightly shorter if I just pass in the method name". (Also, using a method group instead of a delegate prevents caching of delegates the compiler can do otherwise, so it might be less efficient).

在那儿,我希望我能掩盖一下您在所有这些过程中都不了解的地方,或者至少可以在这里指出一点,然后说:那一点,那是我不讨厌的那一点.

There, I hope I covered the bit that you didn't understand in the course of all that, or at least there's a bit here you can point to and say "that bit there, that's the bit I don't grok".

这篇关于将方法传递给LINQ查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆