Skip to main content

LinqGen.Generator by Maxwell Keonwoo Kang

Nuget / site data

NugetNuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: LinqGen.Generator

Package Description

Author: Maxwell Keonwoo Kang

NuGet: https://www.nuget.org/packages/LinqGen.Generator/

https://www.nuget.org/packages/LinqGen/

You can find more details at https://github.com/cathei/LinqGen

Source : https://github.com/cathei/LinqGen

Original Readme

note

LinqGen ⚡

Nuget openupm Discord

Linq meets Source Generator

LinqGen is project to optimize Linq queries using source generation of user code.

It aims to make allocation-free, specialized Linq queries per your type.

Install

Install from NuGet, both LinqGen as library and LinqGen.Generator as incremental source generator.

<ItemGroup>
<PackageReference Include="LinqGen" Version="0.3.1" />
<PackageReference Include="LinqGen.Generator" Version="0.3.1" />
</ItemGroup>

For Unity, you can install as git package from Unity Package Manager.

https://github.com/cathei/LinqGen.git?path=LinqGen.Unity/Packages/com.cathei.linqgen

Or install via OpenUPM.

openupm add com.cathei.linqgen

Any questions?

Feel free to make an issue, or ask me directly from Discord!

Usage

Just add Gen() in front of your Linq query. It will generate code to ensure zero-allocation, may have slightly better performance.

using Cathei.LinqGen;

int[] array = new int[] { 1, 2, 3, 4, 5 };

int result = array.Gen()
.Where(x => x % 2 == 0)
.Select(x => x * 2)
.Sum();

For additional performance boost, use struct functions with IStructFunction interface.

int result = array.Gen()
.Where(new Predicate())
.Select(new Selector())
.Sum();

This is benchmark result for above code. You can see full benchmark results [here](https://github.com/cathei/LinqGen/docs/BenchmarksResults.

MethodCountMeanErrorStdDevRatioAllocatedAlloc Ratio
ForLoop100000449.8 us4.56 us4.27 us0.50-0.000
ForEachLoop100000444.3 us1.48 us1.39 us0.49-0.000
Linq100000899.8 us5.65 us5.01 us1.00105 B1.000
LinqGenDelegate100000576.2 us4.43 us4.14 us0.641 B0.010
LinqGenStruct100000449.8 us4.06 us3.60 us0.50-0.000

Why not just use struct Linq implementations?

Because of this issue, struct linq implementations with many generics must do runtime lookup. Which makes them not much faster than original Linq.

Also, they have to have bunch of type information and tricks for type inference. Which makes your code hard to read and understand. The error messages or stack trace will be very messy as well.

Using source generation also makes your code friendly for AOT platforms, such as Unity, which has maximum generic depth.

Being source generator makes LinqGen core library much small than other struct linq implementations, though it may grow as user uses Linq operations.

How does LinqGen work?

LinqGen has two part of assembly, LinqGen and LinqGen.Generator. The LinqGen assembly contains a stub method and types, which helps you autocomplete and helps generator infer types.

After you write a Linq query with stub methods, then LinqGen.Generator runs and replace the stub methods with generated methods.

How is it possible, while modifying user code is not allowed with source generators? It's because everything LinqGen.Generator generates designed to be precede over stub methods on overload resolution.

Does LinqGen works with Unity Burst compiler?

Yes! LinqGen is aiming to support Unity Burst compiler. Below code is sample of using LinqGen in Burst-compiled job system.

[BurstCompile(CompileSynchronously = true)]
public struct LinqGenSampleJob : IJob
{
[ReadOnly]
public NativeArray<int> Input;

[WriteOnly]
public NativeArray<int> Output;

public void Execute()
{
int index = 0;

foreach (var item in Input.Gen()
.Select(new Selector())
.Order(new Comparer()))
{
Output[index++] = item;
}
}
}

public struct Selector : IStructFunction<int, int>
{
public int Invoke(int arg) => arg * 10;
}

public struct Comparer : IComparer<int>
{
public int Compare(int x, int y) => x - y;
}

Supported methods (working-in-progress)

List of Linq Operations

Generations

  • Empty
  • Range
  • Repeat

Operations

  • Select
  • Where
  • Cast, OfType
  • Skip, Take, TakeLast
  • SkipWhile, TakeWhile
  • Distinct
  • Order, OrderBy, OrderByDescending
  • ThenBy, ThenByDescending
  • GroupBy
  • Concat
  • Prepend, Append

Evaluations

  • GetEnumerator
  • ToArray, ToList
  • Any, All
  • First, FirstOrDefault
  • Last, LastOrDefault
  • Count
  • Aggregate
  • Sum
    • Supports duck typing with + operator overload
  • Min, Max
  • MinBy, MaxBy

Etc

  • Gen
    • Converts IEnumerable to LinqGen enumerable
  • AsEnumerable
    • Converts LinqGen enumerable to IEnumerable
  • RemoveAll

Limitations

  • Element or key types that used with LinqGen must have at least internal accessibility.
  • Struct enumerable should implement IStructEnumerable<,> interface.
  • LinqGen queries should be treated as anonymous type, it cannot be used as return value or instance member. If you have these needs, use AsEnumerable() to convert.
  • LinqGen may not work well when [InternalsVisibleTo] is used while both assemblies are using LinqGen. It can be solved when this language feature is implemented.

Further readings

About

note

No-alloc for Linq operations

How to use

Example ( source csproj, source files )

This is the CSharp Project that references LinqGen.Generator

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LinqGen" Version="0.3.1" />
<PackageReference Include="LinqGen.Generator" Version="0.3.1" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

// DO NOT EDIT
// Generated by LinqGen.Generator
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Cathei.LinqGen;
using Cathei.LinqGen.Hidden;

namespace Cathei.LinqGen.Hidden
{
// Non-exported Enumerable should consider anonymous type, thus it will be internal
internal struct Gen_wRtaM3 : IInternalStub<int>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Gen_wRtaM3(int[] source_wRtaM3) : this()
{
this.source_wRtaM3 = source_wRtaM3;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Count() => this.source_wRtaM3.Length;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Select_6q5z23 Select(Func<int, int> selector_6q5z23) => new Select_6q5z23(this, selector_6q5z23);
[EditorBrowsable(EditorBrowsableState.Never)]
internal int[] source_wRtaM3;
}
}

namespace Cathei.LinqGen
{
// Extension class needs to be internal to prevent ambiguous resolution
internal static partial class LinqGenExtensions_Gen_wRtaM3
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Gen_wRtaM3 Gen(this int[] source_wRtaM3) => new Gen_wRtaM3(source_wRtaM3);
}
}

Usefull

Download Example (.NET C# )

Share LinqGen.Generator

https://ignatandrei.github.io/RSCG_Examples/v2/docs/LinqGen.Generator

In the same category (EnhancementProject) - 13 other generators

AutoInvoke.Generator

AutoSpectre

BuildInfo

Com

CommandLine

DeeDee

Mediator

PlantUmlClassDiagramGenerator

RSCG_AMS

RSCG_FunctionsWithDI

RSCG_TimeBombComment

RSCG_Wait

ThisAssembly