TypedReference
and ArgIterator
)?I doubt it’s hardcoded in the compiler because the documentation for the error CS0610 states:
There are some types that cannot be used as fields or properties. These types include...
Which in my opinion hints that the set of types like those can be extended -- But I could be wrong.
I've searched a bit on SO and while I understand that throwing a compiler error programmatically can't be done, I could find no source stating that certain "special" types' behaviors couldn't be replicated.
Even if the question is mostly academic, there could be some usages for an answer. For example, it could be useful sometimes to be sure that a certain object's lifetime is constrained to the method block which creates it.
EDIT: RuntimeArgumentHandle
is one more (unmentioned) non-storable type.
EDIT 2: If it can be of any use, it seems that the CLR treats those types in a different way as well, if not only the compiler (still assuming that the types are in no way different from others). The following program, for example, will throw a TypeLoadException
regarding TypedReference*
. I've adapted it to make it shorter but you can work around it all you want. Changing the pointer's type to, say, void*
will not throw the exception.
using System;
unsafe static class Program
{
static TypedReference* _tr;
static void Main(string[] args)
{
_tr = (TypedReference*) IntPtr.Zero;
}
}
Okay. This isn't quite a complete analysis, but I suspect it's a sufficient one for the purposes of determining whether you can do this, even by twiddling IL - which, so far as I can tell, you can't.
I also, on looking at the decompiled version with dotPeek, couldn't see anything special about that particular type/those particular types in there, attribute-wise or otherwise:
namespace System
{
/// <summary>
/// Describes objects that contain both a managed pointer to a location and a runtime representation of the type that may be stored at that location.
/// </summary>
/// <filterpriority>2</filterpriority>
[ComVisible(true)]
[CLSCompliant(false)]
public struct TypedReference
{
So, that done, I tried creating such a class using System.Reflection.Emit:
namespace NonStorableTest
{
//public class Invalid
//{
// public TypedReference i;
//}
class Program
{
static void Main(string[] args)
{
AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("EmitNonStorable"),
AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = asmBuilder.DefineDynamicModule("EmitNonStorable", "EmitNonStorable.dll");
TypeBuilder invalidBuilder = moduleBuilder.DefineType("EmitNonStorable.Invalid",
TypeAttributes.Class | TypeAttributes.Public);
ConstructorBuilder constructorBuilder = invalidBuilder.DefineDefaultConstructor(MethodAttributes.Public);
FieldBuilder fieldI = invalidBuilder.DefineField("i", typeof (TypedReference), FieldAttributes.Public);
invalidBuilder.CreateType();
asmBuilder.Save("EmitNonStorable.dll");
Console.ReadLine();
}
}
}
That, when you run it, throws a TypeLoadException, the stack trace of which points you to System.Reflection.Emit.TypeBuilder.TermCreateClass. So then I went after that with the decompiler, which gave me this:
[SuppressUnmanagedCodeSecurity]
[SecurityCritical]
[DllImport("QCall", CharSet = CharSet.Unicode)]
private static void TermCreateClass(RuntimeModule module, int tk, ObjectHandleOnStack type);
Pointing into the unmanaged parts of the CLR. At this point, not to be defeated, I dug into the shared sources for the reference version of the CLR. I won't go through all the tracing through that I did, to avoid bloating this answer beyond all reasonable use, but eventually, you end up in \clr\src\vm\class.cpp, in the MethodTableBuilder::SetupMethodTable2 function (which also appears to set up field descriptors), where you find these lines:
// Mark the special types that have embeded stack poitners in them
if (strcmp(name, "ArgIterator") == 0 || strcmp(name, "RuntimeArgumentHandle") == 0)
pClass->SetContainsStackPtr();
and
if (pMT->GetInternalCorElementType() == ELEMENT_TYPE_TYPEDBYREF)
pClass->SetContainsStackPtr();
This latter relating to information found in \src\inc\cortypeinfo.h, thus:
// This describes information about the COM+ primitive types
// TYPEINFO(enumName, className, size, gcType, isArray,isPrim, isFloat,isModifier)
[...]
TYPEINFO(ELEMENT_TYPE_TYPEDBYREF, "System", "TypedReference",2*sizeof(void*), TYPE_GC_BYREF, false, false, false, false)
(It's actually the only ELEMENT_TYPE_TYPEDBYREF type in that list.)
ContainsStackPtr() is then in turn used elsewhere in various places to prevent those particular types from being used, including in fields - from \src\vm\class.cp, MethodTableBuilder::InitializeFieldDescs():
// If it is an illegal type, say so
if (pByValueClass->ContainsStackPtr())
{
BuildMethodTableThrowException(COR_E_BADIMAGEFORMAT, IDS_CLASSLOAD_BAD_FIELD, mdTokenNil);
}
Anyway: to cut a long, long, long story short, it would seem to be the case that which types are non-storable in this way is effectively hard-coded into the CLR, and thus that if you want to change the list or provide an IL means to flag up types as non-storable, you're pretty much going to have to take Mono or the shared-source CLR and spin off your own version.
I couldn't find anything in the IL that indicated those types are in any way special. I do know that the compiler has many, many special cases, such as transforming int
/Int32
into the internal type int32
, or many things pertaining to the Nullable
struct. I highly suspect these types are also special cases.
A possible solution would be Roslyn, which I expect would let you create such a constraint.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With