tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

What’s the maximum number of generic parameters for a class in .NET/C#?

4 Oct 2019 3 mins .NET, .NET Core, C#

Pretty stupid question, right? Because if it’s more than, say, 20 or 100, it’s enough. But still. Where is the limit?

We can look at this in, at least, two ways. First is obviously what the runtime is willing to load and the other is what Roslyn is able to compile. And there’s a good chance, that these two limits are the same.

Roslyn

Let’s first check the compiler, because that’s how most people would create such type. Obviously, I’m not going to type that type manually, but I’m going to use T4. With the code below you can easily try that the limit is 65535 or 2^16 - 1 using the current .NET Core 3.0.

<#@ assembly name="System.Core" #>
<#@ assembly name="System.Runtime" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#
IEnumerable<long> Range(long start, long end)
{
	for (var i = start; i < end; i++)
		yield return i;
}
IEnumerable<T> Repeat<T>(T value, long count)
{
	for (var i = 0; i < count; i++)
		yield return value;
}

const int Max = 65535;
var ts = string.Join(", ", Range(0, Max).Select(x => $"T{x}"));
var referenceTypes = string.Join(", ", Repeat("string", Max));
var valueTypes = string.Join(", ", Repeat("int", Max));
#>
namespace ConsoleApp1
{
	class RealGenericClass<<#=ts#>>
	{
	}
	static class RealGenericClass
	{
		public static RealGenericClass<<#=referenceTypes#>> CreateWithReferenceTypes() => new RealGenericClass<<#=referenceTypes#>>();
		public static RealGenericClass<<#=valueTypes#>> CreateWithValueTypes() => new RealGenericClass<<#=valueTypes#>>();
	}
}

Reflection.Emit

Another way, way less common yet somewhat in the middle, is using Reflection.Emit. In my case there’s not much to emit, just define type and try to load it. With the simple code below the process fails again at 65535/2^16 - 1.

static void ReflectionEmit()
{
	var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Dummy"), AssemblyBuilderAccess.Run);
	var moduleBuilder = assemblyBuilder.DefineDynamicModule("Dummy");
	var typeBuilder = moduleBuilder.DefineType("RealGenericClass", TypeAttributes.Class);
	typeBuilder.DefineGenericParameters(Ts().ToArray());
	var generic = typeBuilder.CreateType();
	var t = generic.MakeGenericType(Repeat(typeof(int), Max).ToArray());
	var instance = Activator.CreateInstance(t);
	Console.WriteLine(instance);

	static IEnumerable<string> Ts()
	{
		return Range(0, Max).Select(x => $"T{x}");
	}

	static IEnumerable<long> Range(long start, long end)
	{
		for (var i = start; i < end; i++)
			yield return i;
	}

	static IEnumerable<T> Repeat<T>(T value, long count)
	{
		for (var i = 0; i < count; i++)
			yield return value;
	}
}

Maybe if I create the assembly using some non-.NET-provided library (where the limitations are probably aligned), I might get lucky…

Manual assembly creation using Mono.Cecil

I took the trusty Mono.Cecil library and started generating. Here’s the important part.

var typeDefinition = new Mono.Cecil.TypeDefinition("ConsoleApp1", "RealGenericClass", Mono.Cecil.TypeAttributes.Class, baseType);
foreach (var item in Ts())
{
	typeDefinition.GenericParameters.Add(new Mono.Cecil.GenericParameter(item, typeDefinition));
}
assemblyDefinition.MainModule.Types.Add(typeDefinition);

static IEnumerable<string> Ts()
{
	return Range(0, Max).Select(x => $"T{x}");
}

static IEnumerable<long> Range(long start, long end)
{
	for (var i = start; i < end; i++)
		yield return i;
}

Sadly, although I was able to create type with more than 65535 generic parameters, runtime refuses to load it with known Internal limitation: Too many generic arguments.. Fair enough, I think.

Surprisingly ildasm shows the type. And ILSpy wraps over the 2^16 and shows just two generic arguments for my type with 65538 of these.

Type with 65538 generic arguments in ildasm

Closing

I think 65535 of generic parameters is fair. It’s way beyond what would person type or use manually. And for generated code, well, I think it’s still plenty and if you need more, I’m sure, given it’s generated code, you can find way around it.

What’s the biggest generic type you’ve ever created?

Profile Picture Jiří Činčura is .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.