Article 2MD0J Surprise! Creating an instance of an open generic type

Surprise! Creating an instance of an open generic type

by
jonskeet
from Jon Skeet's coding blog on (#2MD0J)

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<>.Enum 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.


1578 b.gif?host=codeblog.jonskeet.uk&blog=717
External Content
Source RSS or Atom Feed
Feed Location http://codeblog.jonskeet.uk/feed/
Feed Title Jon Skeet's coding blog
Feed Link https://codeblog.jonskeet.uk/
Reply 0 comments