Lambda works in FindAll, but not when using it as an Func (or Expression)
Asked Answered
M

3

1

The code below won't compile:

Func<Person, bool> theLambda = (p) => p.Year >= 1992;

foreach (Person pers in PersonList.FindAll(theLambda))
{
    Console.WriteLine(pers.Name);
}

public class Person
{
    public string Name { get; set; }
    public int Year { get; set; }

    public Person(string Name, int Year )
    {
        this.Name = Name; this.Year = Year;
    }
}  

However, if I replace variable "theLambda" directly with the lambda, then it works just fine. What's going on here? (Be gentle, I'm a novice). Thank you so much in advance!
(1) I read the error message, but it doesn't mean anything to me.
(2) Yes, I can make it work with a Predicate by using the compile() keyword, but that's not the issue here.

Edit: why would anyone downvote this? The question wasn't that bad at all as the problem domain is not of a logic nature indeed. Really people.

Myeshamyhre answered 31/10, 2015 at 15:31 Comment(2)
What's the error message?Dictaphone
Probably because you're using an incorrect type. Try Predicate<Person> instead.Tie
K
1

It works because if you declare the lambda inline the compiler implicitly assigns it the right type, i.e. Predicate<Person>. You don't have to explicitly tell the compiler the lambda type as it knows already that it should take a Person and return a bool if you call FindAll on a List<Person>.

foreach (Person pers in PersonList.FindAll(p => p.Year >= 1992))
{
    Console.WriteLine(pers.Name);
}

You can also use Enumerable.Where - LINQ method with the same functionality to make it a bit more readable:

foreach (Person pers in PersonList.Where(p => p.Year >= 1992))
{
    Console.WriteLine(pers.Name);
}

From msdn:

When writing lambdas, you often do not have to specify a type for the input parameters because the compiler can infer the type based on the lambda body, the parameter’s delegate type, and other factors as described in the C# Language Specification. For most of the standard query operators, the first input is the type of the elements in the source sequence. So if you are querying an IEnumerable<Customer>, then the input variable is inferred to be a Customer object

The confusing part is that a Predicate is logically a Func that takes an object of some type T and returns a bool, but for some reason this typing doesn't work and you have to use Predicate<T>. Declaring the lambda function inline avoids this confusion as you just write the lambda body and let the compiler infer the type on its own.

Kayleigh answered 31/10, 2015 at 16:50 Comment(13)
@Praveen You absolutely nailed the problem-domain I don't understand! So inline works and you explained why. Even though i passed a lambda, not a predicate, the compiler knows what to do. So it's a compiler thing. Gotcha! So when I do this: Func<Person, bool> theLambda = (p) => p.Year >= 1992; or this Expression<Func<Person, bool>> theLambda = (p) => p.Year >= 1992 the compiler suddenly can't do it? Is that it?Myeshamyhre
@Myeshamyhre - Lambda is just a name for an anonymous function, a Predicate, or Func, or Action is this anonymous function's type. The whole point of lambdas is that you don't have to explicitly declare their types when the compiler knows that type from the context where you are using them - like in this example. Check this article on msdn - msdn.microsoft.com/en-us/library/bb397687.aspxKayleigh
but for some reason declaring the lambda this way doesn't work and you have to use Predicate<T> <...snip comment...> Exactly. In fact, a Predicate is also an expression (!) returning a bool! It's probably because predicate<T> is not a predicate at all, but something different. Hence the reason why it must be compiled first to convert it from an expression? In that case, the term Predicate is confusing and should have been called PredicateTree<T> or something.Myeshamyhre
Predicate is just a specific case of a Func - it's a Func that takes an object and returns a bool, that's why you don't have to write it in its declaration. Predicate<Person> takes a parameter of type Person and returns a bool, although you declare it Predicate<Person>, not Predicate<Person, bool>Kayleigh
Once again you nailed the problem domain. So once again, why doesn't Func<Person, bool> theLambda = (p) => p.Year >= 1992; work as it meets the requirement to return the bool? Yet passing it (the lambda) inline works fine. That's inconsistent.Myeshamyhre
Logically it should work as Predicate<Person> is exactly a Func<Person, bool>, I don't know why it doesn't, but if you declare the lambda inline you avoid this confusionKayleigh
@Myeshamyhre a lambda has no type ; but a lambda is convertible to any delegate type which match it's signature ; so the same lambda can be converted to Func<T, bool> or Predicate<T> but those two delegates type aren't the same (delegate don't have structural identity but name identity). So when used inline the lambda is converted to a Predicate because it's a valid conversion (and probably overload resolution nailed down the Predicate choice) ; but when using a Func there is a type mismatchLongspur
@Longspur Func<T, bool> predicateDelegate = x => x.stuff > 5; is not a conversion to the delegate, but an assignment. Passing predicateDelegate to a predicate should work just perfectly but doesn't. It must be some kind of a policy decision by Microsoft, not a logical consequence.Myeshamyhre
@Myeshamyhre read this (it's about methog group rather than lambda but the logic is the same ; and i'm as good as Eric Lippert to explain ^^) ; has for the "a lambda has no type" part read this (around middle of article).Longspur
... And I add this from there "The lambda expression can be converted to a delegate of that type because (...)" "In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted."Longspur
@Longspur Fair enough. Still it means that passing a bool-returning Func provides enough information for conversion if passing the lambda works too. Thiis basic Modus Barbara. Maybe some compatibility issues? In that case I would be out of my league as I'm a novice and know nothing about how it used to be.Myeshamyhre
@Myeshamyhre in the first link ; Eric Lippert mention it : This is unfortunate, and if we had to design the whole language and runtime type system over again from scratch, likely this would be made to work. (and damn me, I forgot the negation in my previous comment ; and now I'm implying I'm as good as him ...) Maybe if he goes around he could add information about the motives behind those choices [Note also that in VB.Net the code would have worked : different design team, different design choice ;)]Longspur
@Longspur That was the final nudge towards the answer of my main question. I read that sentence too but couldn't interpret its relevance. I'm a novice remember? All I got is some logic thinking if I may say so :). Anyway, problem solved. I cannot reward you (yet) but rest assured I'm grateful to you and everybody else trying to help me. Thank you all!Myeshamyhre
D
0

The FindAll expects a Predicate and not a Function as seen in the method definition Array.FindAll<T> Method (T[], Predicate<T>)

When you try to pass theLambda it is trying to pass a Function, when the method expects a Predicate. You can instead try defining theLambda as

Predicate<Person> theLambda = (p) => p.Year >= 1992;

A Predicate is a Function that returns a Boolean and that is what is required by the FindAll method to filter the results.

Dictaphone answered 31/10, 2015 at 15:39 Comment(0)
K
-1

Based on the answer here, you can do the following.

foreach (Person pers in PersonList.FindAll(new Predicate<Person>(theLambda)))

Knot answered 31/10, 2015 at 15:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.