Question

I'm calling a method which I do not own that returns a 'Task' object. I want to return that object from my method, but I also need to override the Exception property of that object. If the underlying task catches an exception, and my user retrieves that exception via the Exception property, I need to tweak the data they see in the returned exception.

I'm wondering if there is a better solution to this problem than to do a copy via reflection. I don't really want to create a new instance, but I can't find a way to avoid it.

Another possibility is to wrap the Task object in my own class. But then my method won't be returning a Task (or derived from) object, which may cause difficulty downstream. Not to mention the volume of code to implement all of Task's public members and do nothing but call the base versions.

The code below doesn't work, obviously, but conceptually it's what I want to do:

public class MyTask : Task
{
    override Exception Exception { get { return Tweak(base.Exception); } }
}

public MyTask MyMethod()
{
    Task t = MethodIDontOwnThatReturnsTask();
    return (MyTask) t;     // Can I do this type of operation without copying t?
}
Was it helpful?

Solution 2

Rather than trying to mutate the existing task, or change the way it's seen, just create a new Task (which is what ContinueWith will do). They're not really expensive:

public Task MyMethod()
{
    return MethodIDontOwnThatReturnsTask()
        .ContinueWith(t => { throw Tweak(t.Exception); }
        , TaskContinuationOptions.OnlyOnFaulted);
}

OTHER TIPS

No, you can't change the type of an object once it's been created.

I'd also be reluctant to perform a copy via reflection. I would be tempted to create a new TaskCompletionSource, and add a continuation from the returned task to set the result on the TaskCompletionSource appropriately, tweaking the exception as you go.

I've got some code to do this already, in the case where you've actually got a Task<T>:

var tcs = new TaskCompletionSource<SomeResultType>();
task.ContinueWith(completed =>
{
    PropagateResult(completed, tcs);
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
...

// Generally useful method...
private static void PropagateResult<T>(Task<T> completedTask,
    TaskCompletionSource<T> completionSource)
{
    switch (completedTask.Status)
    {
        case TaskStatus.Canceled:
            completionSource.TrySetCanceled();
            break;
        case TaskStatus.Faulted:
            // This is the bit you'd want to tweak
            completionSource.TrySetException(completedTask.Exception.InnerExceptions);
            break;
        case TaskStatus.RanToCompletion:
            completionSource.TrySetResult(completedTask.Result);
            break;
        default:
            throw new ArgumentException("Task was not completed");
    }
}

Or if you're using C# 5 you could even just use:

public async Task MyMethod()
{
    Task t = MethodIDontOwnThatReturnsTask();
    try
    {
        await t;
    }
    catch(Exception e)
    {
        throw Tweak(e);
    }
}

Note that if you need multiple exceptions to be thrown, this may end up being more problematic - it depends on your exact context.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top