C#
Task\``<TResult>`\`
System.Object
type casting
asynchronous programming

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.

Browse interview questions

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

csharp
1Task<string> stringTask = Task.FromResult("hello");
2
3// COMPILE ERROR — Task<string> is not assignable to Task<object>
4Task<object> objectTask = stringTask;
5// Error CS0029: Cannot implicitly convert type 'Task<string>' to 'Task<object>'
6
7// Explicit cast also fails
8Task<object> objectTask = (Task<object>)stringTask;
9// Error CS0030: Cannot convert type 'Task<string>' to 'Task<object>'

This fails because Task<T> is a concrete class. Covariance (out T) only works on interfaces like IEnumerable<out T>.

csharp
1// Generic helper to convert Task<T> to Task<object>
2static async Task<object> AsObjectTask<T>(Task<T> task)
3{
4    return await task;  // TResult is implicitly cast to object
5}
6
7// Usage
8Task<string> stringTask = GetStringAsync();
9Task<object> objectTask = AsObjectTask(stringTask);
10
11object result = await objectTask;  // "hello" as object

Solution 2: ContinueWith

csharp
1Task<string> stringTask = Task.FromResult("hello");
2
3// ContinueWith transforms the result
4Task<object> objectTask = stringTask.ContinueWith(t => (object)t.Result);
5
6object result = await objectTask;  // "hello"

ContinueWith creates a new task that runs when the original completes, casting the result.

Solution 3: Extension Method

csharp
1public static class TaskExtensions
2{
3    public static async Task<object> AsObjectTask<T>(this Task<T> task)
4    {
5        return await task;
6    }
7
8    public static async Task<TTarget> Cast<TSource, TTarget>(
9        this Task<TSource> task) where TSource : TTarget
10    {
11        return await task;
12    }
13}
14
15// Usage
16Task<string> stringTask = GetStringAsync();
17Task<object> objectTask = stringTask.AsObjectTask();
18
19// Or with type constraint
20Task<object> objectTask2 = stringTask.Cast<string, object>();

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.

csharp
1// Task<T> inherits from Task (non-generic)
2Task<string> stringTask = GetStringAsync();
3Task<int> intTask = GetIntAsync();
4
5// Both can be stored as Task
6List<Task> tasks = new List<Task> { stringTask, intTask };
7await Task.WhenAll(tasks);
8
9// Retrieve results by casting back
10string s = ((Task<string>)tasks[0]).Result;
11int i = ((Task<int>)tasks[1]).Result;

Practical Use Case: Heterogeneous Task Collection

csharp
1public class TaskRunner
2{
3    private readonly List<Func<Task<object>>> _taskFactories = new();
4
5    public void Register<T>(Func<Task<T>> factory)
6    {
7        _taskFactories.Add(async () => await factory());
8    }
9
10    public async Task<List<object>> RunAll()
11    {
12        var tasks = _taskFactories.Select(f => f()).ToList();
13        var results = await Task.WhenAll(tasks);
14        return results.ToList();
15    }
16}
17
18// Usage
19var runner = new TaskRunner();
20runner.Register(() => GetUserAsync());     // Task<User>
21runner.Register(() => GetOrdersAsync());   // Task<List<Order>>
22runner.Register(() => GetConfigAsync());   // Task<Config>
23
24List<object> results = await runner.RunAll();

Why Task<T> Is Not Covariant

csharp
1// IEnumerable<T> IS covariant (declared as IEnumerable<out T>)
2IEnumerable<string> strings = new List<string>();
3IEnumerable<object> objects = strings;  // Works!
4
5// Task<T> is a CLASS — classes cannot declare covariance
6// Only interfaces and delegates support out/in variance in C#
7
8// If Task<T> were covariant, this would be unsafe:
9// Task<object> could be awaited and then incorrectly cast
10// The type system cannot guarantee safety at compile time

Common Pitfalls

  • Assuming Task<Derived> is assignable to Task<Base>: C# generics on classes are invariant. Task<string> cannot be assigned to Task<object> even though string is an object. Always use an explicit conversion helper.
  • Using .Result inside the cast: ContinueWith(t => (object)t.Result) blocks if the original task has not completed. In UI or ASP.NET contexts, this can deadlock. Prefer async/await based conversion to avoid blocking.
  • Forgetting to propagate exceptions: A simple ContinueWith(t => (object)t.Result) does not handle faulted tasks gracefully — accessing .Result on a faulted task throws AggregateException. The async/await helper 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 Task and losing type safety: Storing everything as Task and casting back with ((Task<string>)task).Result is fragile — a wrong cast throws InvalidCastException at runtime. Prefer strongly-typed collections or discriminated unions where possible.

Summary

  • Task<TResult> is not covariant — Task<string> cannot be assigned to Task<object>
  • Use an async helper 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/await conversion over ContinueWith to properly propagate exceptions

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track 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.

Browse interview questions

All Rights Reserved.