4
public Data GetCurrent(Credentials credentials)
{ 
    var data = new Lazy<Data>(() => GetCurrentInternal(credentials));
    try
    {
        return data.Value;
    }
    catch (InvalidOperationException ex)
    {
        throw ex.InnerException;
    }
}

If I change the call to the following:

var data = new Task<Data>(() => GetCurrentInternal(credentials));

Is there anything change? Should I prefer Task over Lazy? What about Dispose() and catch(Exception) ?

  • 2
    Why do you want to use `Lazy` in first place? It make no sense to initialize local variable. See [this](http://stackoverflow.com/q/6847721/1997232). – Sinatr Dec 18 '15 at 13:44
  • 4
    `Task` and `Lazy` are completely different classes and concepts - unrelated to each other. What behaviour do you want? – Baldrick Dec 18 '15 at 13:44
  • 15
    Don't be lazy, read the documentation... https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx –  Dec 18 '15 at 13:45
  • Using a Task like that doesn't compile so the entire question seems a bit hypothetical. – Lasse V. Karlsen Dec 18 '15 at 13:45
  • It compile if I change `.Value => .Result` –  Dec 18 '15 at 13:49
  • What's your query about `Dispose` and `catch`? – weston Dec 18 '15 at 13:54
  • Here OP seems really lazy to check in the specification. – Rahul Dec 18 '15 at 13:54
  • @ivan_petrushenko, care to answer my question? I am curious who told you to write such method or where did you get the idea to ask then such a un-deliberated (in my opinion) question which many think is a good one. – Sinatr Dec 18 '15 at 14:01
  • 1
    If you use `Task` it will throw an `AggregationException` that wraps any exceptions thrown by the enclosed code. – juharr Dec 18 '15 at 14:02
  • @juharr Thanks, for constructive answer –  Dec 18 '15 at 14:03
  • 4
    When you rethrow an inner exception like you do in your code you lose the stack trace making it hard to understand where the original exception occured. In general, don't rethrow exceptions like that. Either 1) catch it and handle it, 2) catch it and rethrow it as a new exception with the caught exception as the inner exception or 3) if you really want to rethrow the original exception (like if you want to log during exception unwinding) use `throw` in the `catch` block without specifying any exception to rethrow the original exception. – Martin Liversage Dec 18 '15 at 14:11
  • @MartinLiversage Thank you very much! –  Dec 18 '15 at 14:13
  • 7
    You are doing your taxes. `Lazy` is "I might not need to compute my charitable contributions this year so I am going to put off doing that work as long as possible, in the hopes that I don't have to do it at all. But if I do have to, I will remember the result I computed so that I don't have to do it again." `Task` is "my darling, while I am doing this other calculation can you work out our charitable contributions? Let me know when you're done, and I may or may not end up using your work. Regardless, once you're done, remember what the result was." – Eric Lippert Dec 18 '15 at 14:41
  • I learnt a lot from the contrast with F#'s Async in this article http://tomasp.net/blog/csharp-async-gotchas.aspx/ – Ruben Bartelink May 25 '16 at 10:45
  • See also http://stackoverflow.com/questions/33872588/caching-the-result-from-a-n-async-factory-method-iff-it-doesnt-throw/33874601 – Ruben Bartelink May 25 '16 at 10:46

3 Answers3

13

Similarities

Both Lazy<T> and Task<T> promise to do some work later and return a result of type T.

Differences

Lazy<T> promises to do its work as late as possible if it is required at all, and does so synchronously.

Task<T> however can do its work asynchronously while your thread does other work, or blocks awaiting result.

Lazy<T> will bubble up any exception caused by the lambda when you call .Value.

Task<T> will keep any exception caused by the lambda and throw it later when you await task. Or if you task.Wait() it may wrap the exception in an AggregationException. I refer you to this for more info on catching exceptions thrown by tasks: Catch an exception thrown by an async method and this http://stiller.co.il/blog/2012/12/task-wait-vs-await/

weston
  • 54,145
  • 21
  • 145
  • 203
  • 4
    there is another similarty: both does no sense in the supplied code. – Mikey Dec 18 '15 at 13:53
  • And I need to call Dispose if I use Task inside finally. Am I right? –  Dec 18 '15 at 14:05
  • 1
    @ivan_petrushenko Probably not please read http://blogs.msdn.com/b/pfxteam/archive/2012/03/25/10287435.aspx – weston Dec 18 '15 at 14:08
  • CA2000 Dispose objects before losing scope. ... before all references to it are out of scope. –  Dec 18 '15 at 14:14
  • 1
    @ivan_petrushenko Just suppress the CA warning. It's not going to stop you from compiling the code. – juharr Dec 18 '15 at 14:16
  • @weston Code Analysis is part of Visual Studio. It use to be in a separate MS product called FXCop. – juharr Dec 18 '15 at 14:17
  • @juharr yes, but if I will use `Lazy` I will not suppress anything –  Dec 18 '15 at 14:17
  • @ivan_petrushenko So? Are you saying that should be listed as a difference? – juharr Dec 18 '15 at 14:19
  • @ivan_petrushenko The two classes are different tools for different jobs. Don't let CA dictate which is appropriate for you. – weston Dec 18 '15 at 14:21
  • 4
    Right, that's a bug in the analyzer that really ought to be fixed. It should suppress the warning when the disposable is a task. – Eric Lippert Dec 18 '15 at 14:23
  • @juharr didn't know that thanks. Bit shocking that Task causes this warning. – weston Dec 18 '15 at 14:23
12

No, Lazy<T> is not the same as Task<T>.


Lazy<T> is an implementation of the concept of lazy evaluation:

  1. It represents a T value that will be computed synchronously at first access, which may or may not occur at all.
  2. Evaluation happens at first access, and subsequent accesses use a cached value. Memoization is a closely related concept.
  3. The first access will block in order to compute the value, while subsequent accesses will yield the value immediately.

Task<T> is related to the concept of a future.

  1. It represents a T value that will be computed (typically asynchronously) at the creation of its promise, even if nobody ends up actually accessing it.
  2. All accesses yield a cached value that has been or will be computed by the evaluation mechanism (the promise) at a prior or future time.
  3. Any access that occurs before the computation of the value has finished will block until the calculation is complete. Any access that occurs after the computation of the value has finished will immediately yield the computed value.

All that being said, Lazy<T> and Task<T> do have something in common that you may have already picked up.

Each of these types is a unary generic type that describes a unique way to perform a particular computation that would yield a T value (and that computation can conveniently be passed as a delegate or lambda). In other words, each of these types is an example of a monad. You can find some very good and simple explanations of what a monad is here on Stack Overflow and elsewhere.

Community
  • 1
  • 1
Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • 3
    Oh, it's a monad all right -- or, more accurately, a comonad. The C# type system has no problem creating types that use the monad pattern. The concept that the C# type system lacks is there is no way to say "I'd like to make a method whose arguments are only generic types that match the monad pattern". – Eric Lippert Dec 18 '15 at 14:26
  • @Thomas Great answer! – Yuval Itzchakov Dec 18 '15 at 14:53
4

Task and Lazy are completely different concepts.

You use Task to do asynchronous operations. Some boring MSDN:

The Task class represents a single operation that does not return a value and that usually executes asynchronously. Task objects are one of the central components of the task-based asynchronous pattern first introduced in the .NET Framework 4. Because the work performed by a Task object typically executes asynchronously on a thread pool thread rather than synchronously on the main application thread, you can use the Status property, as well as the IsCanceled, IsCompleted, and IsFaulted properties, to determine the state of a task. Most commonly, a lambda expression is used to specify the work that the task is to perform.

Lazy is used for deferred initialization of an object. It means, that your object gets initialized only when you call lazyObj.Value.

Use lazy initialization to defer the creation of a large or resource-intensive object, or the execution of a resource-intensive task, particularly when such creation or execution might not occur during the lifetime of the program.

To prepare for lazy initialization, you create an instance of Lazy. The type argument of the Lazy object that you create specifies the type of the object that you want to initialize lazily. The constructor that you use to create the Lazy object determines the characteristics of the initialization. Lazy initialization occurs the first time the Lazy.Value property is accessed.

Andrei
  • 42,814
  • 35
  • 154
  • 218