Question

  • Is there a way to mark a type (or even better, an interface) so that no instances of it can be stored in a field (in a similar way to TypedReference and ArgIterator)?
  • In the same way, is there a way to prevent instances from being passed through anonymous methods and -- In general -- To mimic the behavior of the two types above?
  • Can this be done through ILDasm or more generally through IL editing? Since UnconstrainedMelody achieves normally unobtainable results through binary editing of a compiled assembly, maybe there's a way to "mark" certain types (or even better, abstract ones or marker interfaces) through the same approach.

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;
    }
}
Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top