This is a brief post documenting a very weird thing I partly came up with on Stack Overflow today.
The context is this question. But to skip to the shock, we end up with code like this:
object x = GetWeirdValue(); // This line prints True. Be afraid - be very afraid! Console.WriteLine(x.GetType().GetTypeInfo().IsGenericTypeDefinition);
That just shouldn’t happen. You shouldn’t be able to create an instance of an open type – a type that still contains generic type parameters. What does a List<T>
(rather than a List<string>
or List<int>
) mean? It’s like creating an instance of an abstract class.
Before today, I’d have expected it to be impossible – the CLR should just not allow such an object to exist. I now know one – and only one – way to do it. While you can’t get normal field values for an open generic type, you can get constants… after all, they’re constant values, right? That’s fine for most constants, because those can’t be generic types – int
, string
etc. The only type of constant with a user-defined type is an enum. Enums themselves aren’t generic, of course… but what if it’s nested inside another generic type, like this:
class Generic<T> { enum GenericEnum { Foo = 0 } }
Now Generic<>.GenericEnum
is an open type, because it’s nested in an open type. Using Enum.GetValues(typeof(Generic<>.GenericEnum))
fails in the expected way: the CLR complains that it can’t create instances of the open type. But if you use reflection to get at the constant field representing Foo
, the CLR magically converts the underlying integer (which is what’s in the IL of course) into an instance of the open type.
Here’s the complete code:
using System; using System.Reflection; class Program { static void Main(string[] args) { object x = GetWeirdValue(); // This line prints True Console.WriteLine(x.GetType().GetTypeInfo().IsGenericTypeDefinition); } static object GetWeirdValue() => typeof(Generic<>.GenericEnum).GetTypeInfo() .GetDeclaredField("Foo") .GetValue(null); class Generic<T> { public enum GenericEnum { Foo = 0 } } }
… and the corresponding project file, to prove it works for both the desktop and .NET Core…
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFrameworks>netcoreapp1.0;net45</TargetFrameworks> </PropertyGroup> </Project>
Use this at your peril. I expect that many bits of code dealing with reflection would be surprised if they were provided with a value like this…
It turns out I’m not the first one to spot this. (That would be pretty unlikely, admittedly.) Kirill Osenkov blogged two other ways of doing this, discovered by Vladimir Reshetnikov, back in 2014.
That’s a quite interesting thing to note down , but in general , what are the real case scenarios this will actually help you .. can you provide any use case if possible
LikeLike
No, none whatsoever. This is purely a curiosity.
LikeLike
We should start a specialized StackExchange or Discourse or something specifically for stuff like this.
LikeLike
I think this makes my head hurt just a little bit. Or a lot.
LikeLike
This is bug – it should not be possible.
https://github.com/dotnet/coreclr/issues/11299
Thanks!
LikeLike
You always find the weirdest loopholes. It’s what keeps me coming back.
LikeLike
Which class is used in the code example?
LikeLike
The complete code is in the post. I’m not sure what you’re asking…
LikeLike
Isn’t that simply an “automatic type inference” to typeof (object) …?
Hence, what you have, is an invocation to
GetWeirdValue<object>()
…?LikeLike
I suppose you meant Generic<>.GenericEnum is an open type.
LikeLike
Yes, fixing now.
LikeLike