Last active
March 13, 2022 17:01
-
-
Save JasonBock/b46ab43c29deb43fc7cf41e214627452 to your computer and use it in GitHub Desktop.
Figuring Out How Many Generic Parameters Can Be Defined in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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