Skip to content

Instantly share code, notes, and snippets.

@JasonBock
Last active March 13, 2022 17:01
Show Gist options
  • Save JasonBock/b46ab43c29deb43fc7cf41e214627452 to your computer and use it in GitHub Desktop.
Save JasonBock/b46ab43c29deb43fc7cf41e214627452 to your computer and use it in GitHub Desktop.
Figuring Out How Many Generic Parameters Can Be Defined in C#
/*
I was curious to see what the maximum number of generic parameters were allowed for a method.
This was inspired by https://www.tabsoverspaces.com/233892-whats-the-maximum-number-of-arguments-for-method-in-csharp-and-in-net
To run this code, make a C# console application with the .csproj file looking like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" />
</ItemGroup>
</Project>
The code goes into the Program.cs file.
It seems like the limit is 2^15, or 32768.
Anything past that and I'd get an IndexOutOfRangeException.
I'm guessing this limit is defined somewhere within Roslyn...
curious to see where that constant is :)
*/
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
const int GenericCount = 32768;
var code = GenerateCode(GenericCount);
//Console.Out.WriteLine(code);
if(TryParse(code, out var tree) && TryCompile(tree, out var assemblyData))
{
InvokeMethod(GenericCount, assemblyData);
}
static string GenerateCode(int genericArgumentCount) =>
$@"public static class Code
{{
public static void CallThis<{string.Join(", ", Enumerable.Range(0, genericArgumentCount).Select(_ => $"T{_}"))}>() {{ }}
}}";
static bool TryParse(string code, [MaybeNullWhen(false)] out SyntaxTree tree)
{
var unit = SyntaxFactory.ParseCompilationUnit(code);
if(unit.ContainsDiagnostics)
{
foreach (var diagnostic in unit.GetDiagnostics())
{
Console.Out.WriteLine(diagnostic.GetMessage());
}
tree = null;
return false;
}
else
{
tree = unit.SyntaxTree;
return true;
}
}
static bool TryCompile(SyntaxTree tree, [MaybeNullWhen(false)] out byte[] assemblyData)
{
var references = new List<MetadataReference>();
foreach (var reference in ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!).Split(Path.PathSeparator))
{
references.Add(MetadataReference.CreateFromFile(reference));
}
var compilation = CSharpCompilation.Create("LotsOfGenerics.dll",
new[] { tree },
references: references.ToArray(),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using var stream = new MemoryStream();
var result = compilation.Emit(stream);
if (!result.Success)
{
foreach (var diagnostic in result.Diagnostics)
{
Console.Out.WriteLine(diagnostic.GetMessage());
}
assemblyData = null;
return false;
}
else
{
stream.Position = 0;
assemblyData = stream.ToArray();
return true;
}
}
static void InvokeMethod(int GenericCount, byte[] assemblyData)
{
var assembly = Assembly.Load(assemblyData);
var method = assembly.GetType("Code")!.GetMethod("CallThis")!
.MakeGenericMethod(Enumerable.Range(0, GenericCount).Select(_ => typeof(int)).ToArray());
method.Invoke(null, null);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment