Table of Contents

CHT005: Direct ValueTask.Result access

Cause

The .Result property is accessed directly on a ValueTask<T>.

Rule Description

Accessing .Result directly on a ValueTask<T> is undefined behavior when the ValueTask is backed by an IValueTaskSource. This pattern:

  1. Is undefined behavior for IValueTaskSource-backed ValueTasks
  2. May block if the operation hasn't completed
  3. Consumes the ValueTask, preventing further usage
  4. May throw or return incorrect results

The pooled primitives in CryptoHives.Foundation.Threading use IValueTaskSource for their ValueTask implementations, making this pattern particularly dangerous.

How to Fix

Convert to async code:

// Before
ValueTask<int> vt = GetValueAsync();
int result = vt.Result; // Warning

// After
int result = await GetValueAsync();

Option 2: Convert to Task first

If you must access .Result synchronously:

// Before
ValueTask<int> vt = GetValueAsync();
int result = vt.Result; // Warning

// After
int result = GetValueAsync().AsTask().Result;

When to Suppress

Only suppress if you can guarantee:

  1. The ValueTask is already completed (IsCompletedSuccessfully == true)
  2. You understand the ValueTask will still be consumed
ValueTask<int> vt = GetValueAsync();
if (vt.IsCompletedSuccessfully)
{
#pragma warning disable CHT005
    int result = vt.Result; // Safe because already completed
#pragma warning restore CHT005
}

Example

Violating Code

public int GetValue()
{
    ValueTask<int> vt = GetValueAsync();
    return vt.Result; // CHT005: undefined behavior
}

Fixed Code

// Option 1: Make async
public async Task<int> GetValueAsync()
{
    return await GetValueInternalAsync();
}

// Option 2: Use AsTask()
public int GetValue()
{
    return GetValueInternalAsync().AsTask().Result;
}

// Option 3: Check completion
public int GetValue()
{
    ValueTask<int> vt = GetValueInternalAsync();
    if (vt.IsCompletedSuccessfully)
    {
        return vt.Result;
    }
    return vt.AsTask().Result;
}

See Also