-1
 [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);
    }

Hi, I'm doing this in ASP.NET Core with C#.

Vague part for me is this: _db.Posts.FirstOrDefault(x => x.Key == key);

So what I'm guessing is that:

  1. execute FirstOrDefault method.
  2. parameter x is passed (I don't what it is being passed exactly though).
  3. then, compare x.Key with key
  4. what is next step?
NineBerry
  • 26,306
  • 3
  • 62
  • 93
  • 4
    Did you read the docs for [FirstOrDefault](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.firstordefault?view=netframework-4.8#System_Linq_Enumerable_FirstOrDefault__1_System_Collections_Generic_IEnumerable___0__System_Func___0_System_Boolean__)? – Sweeper Oct 22 '19 at 16:57
  • 5
    Possible duplicate of [Understanding how lambda expression works](https://stackoverflow.com/questions/15094098/understanding-how-lambda-expression-works). Or [What's the point of a lambda expression?](https://stackoverflow.com/q/5873603/8967612). – 41686d6564 stands w. Palestine Oct 22 '19 at 16:58
  • @Sweeper Although the documentation of the method is important to read, I think the OP's problem is that they don't understand how Lambda works in general. The docs of `FirstOrDefault` doesn't explain that extensively (it's still useful to read though). – 41686d6564 stands w. Palestine Oct 22 '19 at 17:04

4 Answers4

3
  1. parameter x is passed (I don't what it is being passed exactly though).

No, this does not happen. What is passed is an expression defining an anonymous function. Such expressions, when using the => operator, are commonly called lambda expressions. x is the part of the expression which determines how the function is called. It's a placeholder for the input variable used by the function expression.

It will help you understand if I give you a pretend version of how the FirstOrDefault() method might be implemented:

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

Some things to understand in that code:

this in front of the first parameter turns the function into an extension method. Instead of calling the method with two arguments, you skip the first argument... call it with only the second argument as if it were a member of the type from the first argument. ie, _db.Posts.FirstOrDefault(foo) instead of FirstOrDefault(_db.Posts, foo).

The key variable in the expression is called a closure. It is available as part of the predicate() function inside this method, even though it's not passed as an argument. This is why the predicate(item) call is able to determine true or false with only item as an input.

The predicate() function call within this method was passed as an argument to the method. That is how the x => x.Key == key argument is interpreted; it becomes the predicate() method used by the FirstOrDefault() function. You can think of it as if predicate() were defined like this:

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

The C# compiler makes this translation for you automatically, and even infers the correct run-time type for T and automatically handles scope for the key closure.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
1

The other answers are close, but not completely correct.

I assume that _db is an Entity Framework DbContext, and _db.Posts is a DbSet<Post>. As such the .FirstOrDefault() method you are seeing is actually an Extension method and the x => x.Key == key part is an Expression tree.

What happens behind the scenes is that the call to _db.Posts.FirstOrDefault(x => x.Key == key) is translated to a SQL statement like SELECT TOP(1) Key, Content, ... FROM posts WHERE Key = @key, the result of which is mapped into a Post entity.

There are a lot of language features at play to make all this work, so let's have a look!

Extension methods

Extension methods are static methods, but can be called like instance methods. They are defined in static classes and have a 'receiver' argument. In the case of FirstOrDefault the extension method looks like this:

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
    }
}

It's usage _db.Posts.FirstOrDefault(...) is actually syntactic sugar and will be translated by the C# compiler to a static method call a la Queryable.FirstOrDefault(_db.Posts, ...).

Note that extension methods are, despite the syntactic sugar, still static methods do not have have access to their receiver's internal state. They can only access public members.

Delegates

C# has support for pseudo-first-class functions, called delegates. There are several ways to instantiate a delegate. They can be used to capture existing methods or they can be initialized with an anonymous function.

The most elegant way to initialize a delegate with an anonymous function is to use lambda style functions like x => x + 10 or (x, y) => x + y. The reason you don't see type annotations in these examples is that the compiler can infer the types of the arguments in many common situations.

Here is another example:

// 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 }

Expression trees

The C# compiler has a feature that allows you to create an Expression tree with normal-looking code.

For example Expression<Func<int, int>> add10Expr = (x => x + 10); will initialize add10Expr not with an actual function but with an expression tree, which is an object graph.

Initialized by hand it would look like this:

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

(which is super cumbersome)

The power of expression trees comes from being able to create, inspect and transform them at runtime.

Which is what Entity Framework does: it translates these C# expression trees to SQL code.

Entity Framework

With all of these features together you can write predicates and other code in C# which gets translated by Entity Framework to SQL, the results of which are "materialized" as normal C# objects.

You can write complex queries to the database all within the comfort of C#. And best of all, your code is statically typed.

M.Stramm
  • 1,289
  • 15
  • 29
0

The x is the range variable of the object you called the function on. The same object you would get in foreach (var x in _db.Posts) It then iterates through that collection looking for x.Key == key and returns the first object that fulfills that. So that function will return the first object in db.Posts where Key == key

edit: corrected term

Skattered
  • 26
  • 4
  • 1
    Sorry, I edited my comment because I jumped the gun... In the lambda, `x` is just its parameter. The range variable is passed to it but OP wouldn't see that part of it. – madreflection Oct 22 '19 at 17:07
  • I looked it up after you commented it and it seemed like it was still the correct term? Are you saying that's not right? – Skattered Oct 22 '19 at 17:18
  • It's about the context in which it's being used. In `foreach (var x in _db.Posts)`, yes, `x` is the range variable. But I spoke too soon regarding how you were using it. It's not the range variable of *"the object you called the function on"*. It's an item in the sequence (_db.Posts is a sequence) you called the function on, which is the range variable in the loop (in `FirstOrDefault`), and the parameter to the lambda (provided by the OP). – madreflection Oct 22 '19 at 17:23
0

Your lambda expression with FirstOrDefault is equivalent to the following extension method

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

X isn't an parameter, its just a shorthand way of referring to the individual item in the collection you are working on like we would have in a foreach statement. The last step in your question is "either return the first Post that has the same key we are comparing against, or return the default value of a Post object (which is null for objects)"

DetectivePikachu
  • 650
  • 3
  • 14