开发者

C# laziness question

What's the common approach to desig开发者_如何学JAVAn applications, which strongly rely on lazy evaluation in C# (LINQ, IEnumerable, IQueryable, ...)?

Right now I usually attempt to make every query as lazy as possible, using yield return and LINQ queries, but in runtime this could usually lead to "too lazy" behavior, when every query gets builts from it's beginning obviously resulting in severe visual performance degradation.

What I usually do means putting ToList() projection operators somewhere to cache the data, but I suspect this approach might be incorrect.

What's the appropriate / common ways to design this sort of applications from the very beginning?


I find it useful to classify each IEnumerable into one of three categories.

  1. fast ones - e.g. lists and arrays
  2. slow ones - e.g. database queries or heavy calculations
  3. non-deterministic ones - e.g. list.Select(x => new { ... })

For category 1, I tend keep the concrete type when appropriate, arrays or IList etc. For category 3, those are best to keep local within a method, to avoid hard-to find bugs. Then we have category 2, and as always when optimizing performance, measure first to find the bottlenecks.


A few random thoughts - as the question itself is loosely defined:

  • Lazy is good only when the result might not be used hence loaded only when needed. Most operations, however, would need the data to be loaded so laziness is not good in that term.
  • Laziness can cause difficult bugs. We have seen it all with data contexts in ORMs
  • Lazy is good when it comes to MEF


Pretty broad question and unfortunately you're going to hear this a lot: It depends. Lazy-loading is great until it's not.

In general, if you're using the same IEnumerables over and over it might be best to cache them as lists.

But rarely does it make sense for your callers to know this either way. That is, if you're getting IEnumerables from a repository or something, it is best to let the repository do its job. It might cache it as a list internally or it might build it up every time. If your callers try to get too clever they might miss changes in the data, etc.


I would suggest doing a ToList in your DAL before returning the DTO

public IList<UserDTO> GetUsers()
{
  using (var db = new DbContext())
  {
    return (from u in db.tblUsers
           select new UserDTO()
           {
               Name = u.Name
           }).ToList();
  }
}

In the example above you have to do a ToList() before the DbContext scope ends.


I you need a certain sequence of data to be cached, call one of the aggregation operators (ToList, ToArray, etc.) on that sequence. Otherwise just use lazy evaluation.

Build your code around your data. What data is volatile and needs to be pulled fresh each time? Use lazy evaluation and don't cache. What data is relatively static and only needs to be pulled once? Cache that data in memory so you don't pull it unnecessarily.


Deferred execution and caching all items with .ToList() are not the only options. The third option is to cache the items while you are iterating by using a lazy List.

The execution is still deferred but all items are only yielded once. An example of how this work:

public class LazyListTest
{
    private int _count = 0;

    public void Test()
    {
        var numbers = Enumerable.Range(1, 40);
        var numbersQuery = numbers.Select(GetElement).ToLazyList(); // Cache lazy
        var total = numbersQuery.Take(3)
            .Concat(numbersQuery.Take(10))
            .Concat(numbersQuery.Take(3))
            .Sum();
        Console.WriteLine(_count);
    }

    private int GetElement(int value)
    {
        _count++;
        // Some slow stuff here...
        return value * 100;
    }
}

If you run the Test() method, the _count is only 10. Without caching it would be 16 and with .ToList() it would be 40!

An example of the implementation of LazyList can be found here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜