Table of Contents

CHT003: ValueTask stored in field

Cause

A ValueTask or ValueTask<T> is stored in a field or auto-property.

Rule Description

Storing a ValueTask in a field creates a high risk of consuming it multiple times. Fields persist beyond the scope of a single method, making it easy to accidentally:

  1. Await the stored ValueTask multiple times
  2. Access the ValueTask from multiple threads
  3. Forget that the ValueTask was already consumed

A ValueTask should be consumed immediately or stored temporarily in a local variable within a single method scope.

How to Fix

Option 1: Store as Task

Change the field type to Task and use AsTask():

// Before
private ValueTask _pendingWork;

public async Task StartAsync()
{
    _pendingWork = DoWorkAsync();
}

// After
private Task? _pendingWork;

public async Task StartAsync()
{
    _pendingWork = DoWorkAsync().AsTask();
}

Option 2: Store the result

If the operation is already completed, store the result instead:

// Before
private ValueTask<int> _cachedValue;

// After
private int _cachedValue;
private bool _hasValue;

Option 3: Use Preserve()

private Task? _pendingWork;

public async Task StartAsync()
{
    _pendingWork = DoWorkAsync().Preserve();
}

When to Suppress

Suppress only if you have a very specific use case where:

  1. The field is only accessed from a single code path
  2. You guarantee single consumption
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CHT003")]
private ValueTask _singleUseTask;

Example

Violating Code

public class MyService
{
    private ValueTask _initTask; // CHT003

    public void Initialize()
    {
        _initTask = InitializeAsync();
    }

    public async Task WaitForInitAsync()
    {
        await _initTask; // Might be called multiple times!
    }
}

Fixed Code

public class MyService
{
    private Task? _initTask;

    public void Initialize()
    {
        _initTask = InitializeAsync().AsTask();
    }

    public async Task WaitForInitAsync()
    {
        if (_initTask != null)
        {
            await _initTask; // Safe - Task can be awaited multiple times
        }
    }
}

See Also