Casting TResult in TaskTResult to System.Object
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
In C#, Task<TResult> is not covariant — you cannot directly assign a Task<string> to a Task<object> even though string inherits from object. This is because Task<T> is a class (not an interface), and C# only supports covariance on generic interfaces and delegates. To convert Task<TResult> to Task<object>, you must unwrap the task, cast the result, and rewrap it. The common approach is using async/await with a helper method or ContinueWith.
The Problem
This fails because Task<T> is a concrete class. Covariance (out T) only works on interfaces like IEnumerable<out T>.
Solution 1: Async Helper Method (Recommended)
Solution 2: ContinueWith
ContinueWith creates a new task that runs when the original completes, casting the result.
Solution 3: Extension Method
Solution 4: Non-Generic Task as Common Type
If you need to store tasks of different types together, use the non-generic Task base class.
Practical Use Case: Heterogeneous Task Collection
Why Task<T> Is Not Covariant
Common Pitfalls
- Assuming
Task<Derived>is assignable toTask<Base>: C# generics on classes are invariant.Task<string>cannot be assigned toTask<object>even thoughstringis anobject. Always use an explicit conversion helper. - Using
.Resultinside the cast:ContinueWith(t => (object)t.Result)blocks if the original task has not completed. In UI or ASP.NET contexts, this can deadlock. Preferasync/awaitbased conversion to avoid blocking. - Forgetting to propagate exceptions: A simple
ContinueWith(t => (object)t.Result)does not handle faulted tasks gracefully — accessing.Resulton a faulted task throwsAggregateException. Theasync/awaithelper naturally propagates the original exception. - Creating unnecessary wrapper tasks: Each conversion creates a new
Task<object>wrapper. In hot paths or tight loops, this adds allocation overhead. If performance is critical, consider redesigning the API to avoid the conversion. - Using non-generic
Taskand losing type safety: Storing everything asTaskand casting back with((Task<string>)task).Resultis fragile — a wrong cast throwsInvalidCastExceptionat runtime. Prefer strongly-typed collections or discriminated unions where possible.
Summary
Task<TResult>is not covariant —Task<string>cannot be assigned toTask<object>- Use an
asynchelper method:async Task<object> AsObjectTask<T>(Task<T> task) => await task; - Use
ContinueWith(t => (object)t.Result)as an alternative (but handle exceptions carefully) - Create an extension method for reusable task casting across your codebase
- Prefer
async/awaitconversion overContinueWithto properly propagate exceptions
Related reading
- Catch a thread's exception in the caller thread?
- Catch a thread's exception in the caller thread?
- Catch an exception thrown by an async void method
- Catch exception in asyncio gather
- center MessageBox in parent form
- Centering controls within a form in .NET Winforms?
- Catch exception in Kotlin async coroutines and stop propagating
- catch exception that is thrown in different thread

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.