I have an Operation
class like...
public sealed class Operation
{
public void DoSomething1(ArgType1 arg) {...}
public void DoSomething2(ArgType2 arg) {...}
...
public Task<bool> Execute() {...}
}
The DoSomething
methods package work to be done, storing the arg parameters and then the Execute()
method will start a Task
to accomplish this work together in an atomic fashion. The primary effect of the DoSomething
s are side effects but some of them make sense to also return a value, my first instinct would be to return a Task
as follows...
public Task<ResultType3> DoSomething3(ArgType3 arg) {...}
but the catch is that that Task
wouldn't be 'live' as most tasks are assumed to be. await
-ing the result of this Task
would be fruitless until Execute()
is called to initiate the work, and thus I feel it would be confusing to the consumer. It is as if the return values of DoSomething3()
and Execute()
are non-independent tasks.
I could wrap the Task<>
in a new type, called something like Result<>
, and internally it would hold a Task<>
and the Operation
would hold onto its TaskCompletionSource<>
and set the Result
at the end of Execute()
so that clients, after await
-ing the Task
returned from Execute
, could observe the Result
.
public Result<T>
{
internal Result(Task t) { _t = t; }
public bool IsComplete { get { return _t.IsComplete; } }
public T Result { get { return _t.Result; } }
// Perhaps more methods delegating to the underlying Task
}
public Result<ResultType4> DoSomething4(ArgType4 arg) {...}
The primary motivation of wrapping Task
would be to communicate to the consumer that the result of DoSomething3()
is not a live Task
and to make it difficult / impossible to call...
var result = await op.DoSomething4(x);
as that would likely deadlock the code, since no one fired off the Operation
yet. Note the similarity of this Result<>
type to Nullable<>
with different semantics.
Another approach would be for the method to return some opaque object that would be used as a key to retrieve the actual result from the Operation
after Execute()
has completed...
var token = op.DoSomething4(x);
...
var succeeded = await op.Execute();
if (! succeeded) return;
var result = op.RetrieveResult(token);
where Retrieve result would have a signature similar to...
public T RetrieveResult(Token<T> token) {...}
I suppose another option would be to add an additional argument that acts as a callback to be executed at the end of Execute()
when the actual result is available...
public void DoSomething5(ArcType5 arg, Func<ResultType5,Task> callback) {...}
So as you can see I have several different options without a strong intuition about which is most appropriate. Unfortunately this is probably primarily just a matter of taste, but I would appreciate feedback on the different approaches.