我对这句话感到困惑。 Lambda运算符?

时间:2019-10-22 16:56:22

标签: c#

 [Route("{year:min(2000)}/{month:range(1,12)}/{key}")]
    public IActionResult Post(int year, int month, string key)
    {
        var post = _db.Posts.FirstOrDefault(x => x.Key == key);            

        return View(post);
    }

嗨, 我正在用C#在ASP.NET Core中做到这一点。

对我来说模糊的部分是:_db.Posts.FirstOrDefault(x => x.Key == key);

所以我猜是:

  1. 执行FirstOrDefault方法。
  2. 参数x已通过(尽管我并没有完全通过它)。
  3. 然后,将x.Key与key进行比较
  4. 下一步是什么?

4 个答案:

答案 0 :(得分:3)

  
      
  1. 参数x已通过(尽管我并没有完全通过它)。
  2.   

不,这不会发生。传递的是表达式,定义了匿名函数。使用=>运算符时,此类表达式通常称为 lambda表达式x是表达式的一部分,它确定函数的调用方式。它是函数表达式使用的输入变量的占位符。

如果我为您提供如何实现FirstOrDefault()方法的假装版本,它将帮助您理解:

public T FirstOrDefault<T>(this IEnumerable<T> items, Func<T, boolean> predicate)
{
    foreach(T item in items)
    {
        if(predicate(item)) return item;
    }
    return default(T);
}

该代码中应理解的一些事情:

第一个参数前面的

this将功能转换为扩展方法。无需使用两个参数调用该方法,而是跳过第一个参数...仅使用第二个参数调用它,就好像它是第一个参数的类型的成员一样。即_db.Posts.FirstOrDefault(foo)代替FirstOrDefault(_db.Posts, foo)

表达式中的key变量称为闭包。即使未将其作为参数传递,它也可以作为此方法内predicate()函数的一部分使用。这就是predicate(item)调用仅用true作为输入就能确定falseitem的原因。

此方法中的predicate()函数调用已作为参数传递给该方法。这就是x => x.Key == key参数的解释方式;它成为predicate()函数使用的FirstOrDefault()方法。您可以认为predicate()是这样定义的:

bool predicate(T x)
{
    return x.Key == key;
}

C#编译器会自动为您进行转换,甚至为T推断正确的运行时类型,并自动处理key闭包的范围。

答案 1 :(得分:1)

其他答案很接近,但并不完全正确。

我假设_dbEntity Framework DbContext,而_db.PostsDbSet<Post>。 因此,您看到的.FirstOrDefault()方法实际上是Extension method,而x => x.Key == key部分是Expression tree

幕后发生的事情是,对_db.Posts.FirstOrDefault(x => x.Key == key)的调用被转换为类似SELECT TOP(1) Key, Content, ... FROM posts WHERE Key = @key的SQL语句,其结果被映射到Post实体中。

有很多语言功能可以使所有这些工作正常进行,所以让我们看一下!

扩展方法

扩展方法是静态方法,但可以像实例方法一样调用。 它们在静态类中定义,并具有“接收器”参数。对于FirstOrDefault,扩展方法如下:

public static class Queryable {
    public static T FirstOrDefault<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate = null) {
        // do something with source and predicate and return something as a result
    }
}

用法_db.Posts.FirstOrDefault(...)实际上是语法糖,它将由C#编译器转换为称为la Queryable.FirstOrDefault(_db.Posts, ...)的静态方法。

请注意,尽管使用了语法糖,但扩展方法仍然是静态方法,无法访问其接收者的内部状态。他们只能访问公共成员。

代表

C#支持称为代理的伪一流函数。有几种实例化委托的方法。 它们可以用于capture现有方法,也可以使用匿名函数进行初始化。

使用匿名函数初始化委托的最优雅的方法是使用lambda样式函数,例如x => x + 10(x, y) => x + y。 在这些示例中看不到类型注释的原因是,在许多常见情况下,编译器可以推断参数的类型。

这里是另一个示例:

// This is a normal function
bool IsEven(int x) {
  return x % 2 == 0;
}

// This is an anonymous function captured in a delegate of type `Func<T1, TResult>`
Func<int, bool> isEven = x => x % 2 == 0;

// You can also capture methods in delegates
Func<int, bool> isEven = IsEven;

// Methods can be called
int a = IsEven(5); // result is false

// Delegates can be called as well
int b = isEven(4); // result is true

// The power of delegates comes from being able to pass them around as arguments
List<int> Filter(IEnumerable<int> array, Func<int, bool> predicate) {
  var result = new List<int>();
  foreach (var n in array) {
    if (predicate(n)) {
      result.Add(n);
    }
  }
  return result;
}

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = Filter(numbers, isEven); // result is a list of { 2, 4, 6 }
var numbersGt4 = Filter(numbers, x => x > 4); // result is a list of { 5, 6 }

表达树

C#编译器具有一项功能,使您可以使用外观正常的代码创建表达式树。

例如,Expression<Func<int, int>> add10Expr = (x => x + 10);不会使用实际函数而是使用表达式树(它是对象图)来初始化add10Expr

手动初始化,看起来像这样:

Expression xParameter = Expression.Parameter(typeof(int), "x");
Expression<Func<int, int>> add10Expr = 
    Expression.Lambda<Func<int, int>>(
        Expression.Add(
            xParameter, 
            Expression.Constant(10)
        ),
        xParameter
    );

(这很麻烦)

表达式树的强大之处在于能够在运行时创建,检查和转换它们。

实体框架的作用是:将这些C#表达式树转换为SQL代码。

实体框架

将所有这些功能结合在一起,您就可以用C#编写谓词和其他代码,然后通过Entity Framework将其转换为SQL,其结果将作为常规C#对象“物化”。

您可以在C#的便利范围内将复杂的查询全部写入数据库。 最好的是,您的代码是静态键入的。

答案 2 :(得分:0)

x是调用函数的对象的范围变量。您将在foreach (var x in _db.Posts)中获得的对象相同,然后遍历该集合以寻找x.Key == key,并返回满足该条件的第一个对象。因此该函数将返回db.Posts中第一个对象,其中Key == key

编辑:更正的字词

答案 3 :(得分:0)

您的带有FirstOrDefault的lambda表达式等效于以下扩展方法

public static Post FirstOrDefault(this YourDBType _db, string Key)
{
    foreach(Post x in _db.Posts)
    {
        if(x.Key == Key)
        {
            return x
        }
    }
    return null
}

X不是参数,它只是引用您正在处理的集合中单个项目的一种简便方法,就像在foreach语句中那样。问题的最后一步是“要么返回与我们比较的键相同的第一个Post,要么返回Post对象的默认值(对象为null)”