Skip to content

Latest commit

 

History

History
163 lines (135 loc) · 4.53 KB

async-enumerable.md

File metadata and controls

163 lines (135 loc) · 4.53 KB

Async Enumerable

Specifying IAsyncEnumerable<T> as the injection type allows instances of all bindings implementing type T to be injected in an asynchronous-lazy manner - the instances will be provided one at a time, in an order corresponding to the sequence of the bindings.

using Shouldly;
using Pure.DI;

DI.Setup(nameof(Composition))
    .Bind<IHealthCheck>().To<MemoryCheck>()
    .Bind<IHealthCheck>("External").To<ExternalServiceCheck>()
    .Bind<IHealthService>().To<HealthService>()

    // Composition root
    .Root<IHealthService>("HealthService");

var composition = new Composition();
var healthService = composition.HealthService;
var checks = await healthService.GetChecksAsync();

checks[0].ShouldBeOfType<MemoryCheck>();
checks[1].ShouldBeOfType<ExternalServiceCheck>();

interface IHealthCheck;

class MemoryCheck : IHealthCheck;

class ExternalServiceCheck : IHealthCheck;

interface IHealthService
{
    Task<IReadOnlyList<IHealthCheck>> GetChecksAsync();
}

class HealthService(IAsyncEnumerable<IHealthCheck> checks) : IHealthService
{
    public async Task<IReadOnlyList<IHealthCheck>> GetChecksAsync()
    {
        var results = new List<IHealthCheck>();
        await foreach (var check in checks)
        {
            results.Add(check);
        }

        return results;
    }
}
Running this code sample locally
dotnet --list-sdk
  • Create a net10.0 (or later) console application
dotnet new console -n Sample
dotnet add package Pure.DI
dotnet add package Shouldly
  • Copy the example code into the Program.cs file

You are ready to run the example 🚀

dotnet run

Note

IAsyncEnumerable provides efficient lazy enumeration for scenarios where you need to process many instances without loading them all into memory at once.

Each element is created when the consumer reaches it, so a health check that fails early can stop the loop and the later checks are never constructed. The order follows the binding declaration order, exactly as with IEnumerable<T>, and here the consumer collects the results into a list of its own. The sequence itself carries no cancellation token: pass the token to the consumer's method and stop the await foreach, as GetChecksAsync does by returning a materialized list. Do not keep the enumerable in a field and enumerate it from several threads.

Tip

Prefer IAsyncEnumerable<T> only when element creation or consumption really awaits work; for plain handlers IEnumerable<T> is cheaper and easier to test.

The following partial class will be generated
partial class Composition
{
  public IHealthService HealthService
  {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      async IAsyncEnumerable<IHealthCheck> EnumerationOf_transientIAsyncEnumerableIHealthCheck()
      {
        yield return new MemoryCheck();
        yield return new ExternalServiceCheck();
        await Task.CompletedTask;
      }

      return new HealthService(EnumerationOf_transientIAsyncEnumerableIHealthCheck());
    }
  }
}

Class diagram:

---
 config:
  class:
   hideEmptyMembersBox: true
---
classDiagram
	MemoryCheck --|> IHealthCheck
	ExternalServiceCheck --|> IHealthCheck : "External"
	HealthService --|> IHealthService
	Composition ..> HealthService : IHealthService HealthService
	HealthService *-- IAsyncEnumerableᐸIHealthCheckᐳ : IAsyncEnumerableᐸIHealthCheckᐳ
	IAsyncEnumerableᐸIHealthCheckᐳ *-- MemoryCheck : IHealthCheck
	IAsyncEnumerableᐸIHealthCheckᐳ *-- ExternalServiceCheck : "External" IHealthCheck
	namespace Pure.DI.UsageTests.BCL.AsyncEnumerableScenario {
		class Composition {
		<<partial>>
		+IHealthService HealthService
		}
		class ExternalServiceCheck {
				<<class>>
			+ExternalServiceCheck()
		}
		class HealthService {
				<<class>>
			+HealthService(IAsyncEnumerableᐸIHealthCheckᐳ checks)
		}
		class IHealthCheck {
			<<interface>>
		}
		class IHealthService {
			<<interface>>
		}
		class MemoryCheck {
				<<class>>
			+MemoryCheck()
		}
	}
	namespace System.Collections.Generic {
		class IAsyncEnumerableᐸIHealthCheckᐳ {
				<<interface>>
		}
	}
Loading

See also: