Why would you use Expression> rather than Func?

后端 未结 10 2000
隐瞒了意图╮
隐瞒了意图╮ 2020-11-22 00:32

I understand lambdas and the Func and Action delegates. But expressions stump me.

In what circumstances would you use an Expression

相关标签:
10条回答
  • 2020-11-22 01:02

    There is a more philosophical explanation about it from Krzysztof Cwalina's book(Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries);

    Edit for non-image version:

    Most times you're going to want Func or Action if all that needs to happen is to run some code. You need Expression when the code needs to be analyzed, serialized, or optimized before it is run. Expression is for thinking about code, Func/Action is for running it.

    0 讨论(0)
  • 2020-11-22 01:03

    I'd like to add some notes about the differences between Func<T> and Expression<Func<T>>:

    • Func<T> is just a normal old-school MulticastDelegate;
    • Expression<Func<T>> is a representation of lambda expression in form of expression tree;
    • expression tree can be constructed through lambda expression syntax or through the API syntax;
    • expression tree can be compiled to a delegate Func<T>;
    • the inverse conversion is theoretically possible, but it's a kind of decompiling, there is no builtin functionality for that as it's not a straightforward process;
    • expression tree can be observed/translated/modified through the ExpressionVisitor;
    • the extension methods for IEnumerable operate with Func<T>;
    • the extension methods for IQueryable operate with Expression<Func<T>>.

    There's an article which describes the details with code samples:
    LINQ: Func<T> vs. Expression<Func<T>>.

    Hope it will be helpful.

    0 讨论(0)
  • 2020-11-22 01:07

    I'm adding an answer-for-noobs because these answers seemed over my head, until I realized how simple it is. Sometimes it's your expectation that it's complicated that makes you unable to 'wrap your head around it'.

    I didn't need to understand the difference until I walked into a really annoying 'bug' trying to use LINQ-to-SQL generically:

    public IEnumerable<T> Get(Func<T, bool> conditionLambda){
      using(var db = new DbContext()){
        return db.Set<T>.Where(conditionLambda);
      }
    }
    

    This worked great until I started getting OutofMemoryExceptions on larger datasets. Setting breakpoints inside the lambda made me realize that it was iterating through each row in my table one-by-one looking for matches to my lambda condition. This stumped me for a while, because why the heck is it treating my data table as a giant IEnumerable instead of doing LINQ-to-SQL like it's supposed to? It was also doing the exact same thing in my LINQ-to-MongoDb counterpart.

    The fix was simply to turn Func<T, bool> into Expression<Func<T, bool>>, so I googled why it needs an Expression instead of Func, ending up here.

    An expression simply turns a delegate into a data about itself. So a => a + 1 becomes something like "On the left side there's an int a. On the right side you add 1 to it." That's it. You can go home now. It's obviously more structured than that, but that's essentially all an expression tree really is--nothing to wrap your head around.

    Understanding that, it becomes clear why LINQ-to-SQL needs an Expression, and a Func isn't adequate. Func doesn't carry with it a way to get into itself, to see the nitty-gritty of how to translate it into a SQL/MongoDb/other query. You can't see whether it's doing addition or multiplication or subtraction. All you can do is run it. Expression, on the other hand, allows you to look inside the delegate and see everything it wants to do. This empowers you to translate the delegate into whatever you want, like a SQL query. Func didn't work because my DbContext was blind to the contents of the lambda expression. Because of this, it couldn't turn the lambda expression into SQL; however, it did the next best thing and iterated that conditional through each row in my table.

    Edit: expounding on my last sentence at John Peter's request:

    IQueryable extends IEnumerable, so IEnumerable's methods like Where() obtain overloads that accept Expression. When you pass an Expression to that, you keep an IQueryable as a result, but when you pass a Func, you're falling back on the base IEnumerable and you'll get an IEnumerable as a result. In other words, without noticing you've turned your dataset into a list to be iterated as opposed to something to query. It's hard to notice a difference until you really look under the hood at the signatures.

    0 讨论(0)
  • 2020-11-22 01:07

    I don't see any answers yet that mention performance. Passing Func<>s into Where() or Count() is bad. Real bad. If you use a Func<> then it calls the IEnumerable LINQ stuff instead of IQueryable, which means that whole tables get pulled in and then filtered. Expression<Func<>> is significantly faster, especially if you are querying a database that lives another server.

    0 讨论(0)
提交回复
热议问题