AsyncIt by Oleg Shilo
NuGet / site data
Details
Info
Name: AsyncIt
AsyncIt is a C# source generator (CodeAnalyzer) for automatic generation of async/sync versions of the type API.
Author: Oleg Shilo
NuGet: https://www.nuget.org/packages/AsyncIt/
You can find more details at https://github.com/oleg-shilo/AsyncIt/
Original Readme
AsyncIt
AsyncIt is a NuGet package library that allows the automatic generation of additional synchronous and asynchronous APIs for existing user codebase and external packages.
It aims to extend user-defined CLR types by automating the otherwise manual process of defining repetitive and straightforward routines. Thus, the development, maintenance and consumption of the released API are simplified due to the balanced (close to ideal) ratio of the synchronous and asynchronous API endpoints:
Every functionality point has both Async and Sync API endpoints available.
This content is an extract from the project's main Wiki page. It is highly recommended that you read it, as it explains the deep reasons behind this project and details the more concrete usage scenarios.
Overview
AsyncIt is a source generator that is integrated into the .NET build process as a special tool type - the so-called "Analyzer". It is invoked by the compiler during the assembly build and allows the injection of missing API endpoints based on the present assembly API. Thus, if the assembly being built has the GetStatus
but not the GetStatusAsync
method, then AsyncIt will generate the missing method with a straightforward implementation. It can also generate the synchronous API if it is not present in the original codebase:
The API defines synchronous methods only:
Original code
public partial class DeviceLib
{
public static string GetStatus() {. . .}
}Code that is fed to the C# compiler
public partial class DeviceLib
{
public static string GetStatus() {. . .}
}
public partial class DeviceLib // AsyncIt generated
{
public static Task<string> GetStatusAsync()
=> TaskRun(() => GetStatus());
}
AsyncIt does not do anything fancy. Like the await
keyword, it cannot magically convert a synchronous routine into an asynchronous one and vice versa. Instead, it simply emits the code that the developer would type manually if he/she decides to use the API in a concurrency way that the API author did not anticipate.
AsyncIt can also be used to balance API of the external assemblies (e.g. .NET base classes, nuget packages)
This is where AsyncIt is placed in the overall .NET concurrency model architecture:
Usage
In order to integrate AsyncIt with your .NET project, add AsyncIt Nuget package.
dotnet add package AsyncIt
That's it. Now, you can mark any type for which you want to generate async/sync methods with the [Async]
attribute (see the details below), and the new source code will be generated and included in the build.
You can always inspect the generated code in the Visual Studio solution explorer:
Extending user-defined types
In this scenario, a user type containing sync/async methods is extended by additional source file(s) implementing additional API methods. The type can be extended either with an additional partial class definition or by the extension methods class.
A typical usage can be illustrated by the code below.
Async scenario:
[Async]
public partial class BankService
{
public partial class OrderService
{
public Order GetOrder(int id) // and GetOrderAsync will be created by AsyncIt
{...}
}
}
...
async Task OnButtonClick(object sender, EventArgs args)
{
Order order = await service.GetOrderAsync(this.OrderId);
orderLabel.Text = order.Name;
}
Sync scenario:
[Async(Interface = Interface.Sync)]
partial class AccountService
{
public async Task<Account> GetAccountAsync(int id) // and GetAccount will be created by AsyncIt
{...}
}
...
static void Main()
{
var account = new AccountService().GetAccount(333);
File.WriteAllText($"account_{account.Id}.txt", account.Balance.ToString());
}
Extending external types
In this scenario, an external type (from a referenced assembly) containing sync/async methods is extended by additional source file(s) implementing additional API methods. The type can be extended by the extension methods class.
A typical usage can be illustrated by the code below for generating on-fly synchronous methods for type HttpClient
.
Async scenario:
// For all synchronous methods of DirectoryInfo will be created an async equivalent by AsyncIt
[assembly: AsyncExternal(typeof(DirectoryInfo), Interface.Async)]
...
async Task OnButtonClick(object sender, EventArgs args)
{
var info = new DirectoryInfo(workingDir);
string[] folders = await info.GetDirectoriesAsync("*", SearchOption.AllDirectories);
foreach(var path in folders)
foldersListBox.Add(path);
}
Sync scenario:
// For all asynchronous methods of HttpClient will be created a sync equivalent by AsyncIt
[assembly: AsyncExternal(typeof(HttpClient), Interface.Sync)];
...
static void Main()
=> File.WriteAllText(
"temperature.txt",
new HttpClient().GetString("https://www.weather.com/au/melbourne/temperature"));
About
Generate async from sync or sync from async
How to use
Example (source csproj, source files)
- CSharp Project
- Program.cs
- Person.cs
This is the CSharp Project that references AsyncIt
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AsyncIt" Version="1.0.0-pre4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
This is the use of AsyncIt in Program.cs
using AsyncDemo;
var p=new Person();
var result=await p.RunAsync();
Console.WriteLine(result);
result=p.Run();
Console.WriteLine(result);
This is the use of AsyncIt in Person.cs
using System.ComponentModel;
namespace AsyncDemo;
[AsyncIt.Async(Interface = AsyncIt.Interface.Sync)]
internal partial class Person
{
public async Task<bool> RunAsync()
{
await Task.Delay(1000);
return true;
}
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- AsyncAttribute.g.cs
- Person.AsyncDemo.Person.g.cs
// <auto-generated/>
using System;
using System.Reflection;
namespace AsyncIt
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AsyncAttribute : Attribute
{
public AsyncAttribute()
{
}
public AsyncAttribute(Algorithm algorithm, Interface @interface)
{
Algorithm = algorithm;
Interface = @interface;
}
public AsyncAttribute(Interface @interface, Algorithm algorithm)
{
Algorithm = algorithm;
Interface = @interface;
}
public AsyncAttribute(Algorithm algorithm)
{
Algorithm = algorithm;
}
public AsyncAttribute(Interface @interface)
{
Interface = @interface;
}
public Algorithm Algorithm { get; set; }
public Interface Interface { get; set; }
internal string TypeGenericArgs;
internal string NamePattern;
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public sealed class AsyncExternalAttribute : Attribute
{
public AsyncExternalAttribute()
{
}
public AsyncExternalAttribute(Type type)
{
Type = type;
}
public AsyncExternalAttribute(Type type, Interface @interface)
{
Type = type;
Interface = @interface;
}
public AsyncExternalAttribute(Type type, Interface @interface, string methods)
{
Type = type;
Interface = @interface;
Methods = methods;
}
public Interface Interface { get; set; }
public Type Type { get; set; }
public string Methods { get; set; } = "*";
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class IgnoreAttribute : Attribute
{
}
public enum Interface
{
Async,
Sync,
Full,
}
public enum Algorithm
{
PartialType,
ExtensionMethods
}
}
// <auto-generated/>
using System.ComponentModel;
namespace AsyncDemo
{
internal partial class Person
{
/// <summary>
/// The synchronous version of <see cref="Person.RunAsync()"/>.
/// </summary>
public bool Run()
=> RunAsync().Result;
}
}
Useful
Download Example (.NET C#)
Share AsyncIt
https://ignatandrei.github.io/RSCG_Examples/v2/docs/AsyncIt