使类型的实例不可存储

时间:2013-01-16 16:02:47

标签: c# compiler-errors il ildasm

  • 有没有办法标记类型(甚至更好的界面),以便不能将任何实例存储在字段中(与TypedReferenceArgIterator类似)?
  • 以同样的方式,有没有办法阻止实例通过匿名方法传递 - 通常 - 模仿上述两种类型的行为?
  • 这可以通过ILDasm或更一般地通过IL编辑来完成吗?由于UnconstrainedMelody通过编译程序集的二进制编辑实现了通常无法获得的结果,因此可能有一种方法可以通过相同的方法“标记”某些类型(甚至更好,抽象的或标记接口)。

我怀疑它在编译器中是硬编码的,因为documentation for the error CS0610状态:

  

有些类型不能用作字段或属性。这些类型包括......

我认为哪些类型可以扩展 - 但我可能错了。

我在SO上搜索了一下,虽然我知道throwing a compiler error无法以编程方式完成,但我找不到任何来源说某些“特殊”类型的行为无法复制。

即使问题主要是学术问题,也可能会有一些答案。例如,有时可以确保某个对象的生命周期被约束到创建它的方法块。

编辑: RuntimeArgumentHandle是另一种(未提及的)不可存储类型。

编辑2:如果它有任何用处,似乎CLR也以不同的方式处理这些类型,如果不仅仅是编译器(仍假设类型不在与其他人不同的方式)。例如,以下程序将针对TypeLoadException抛出TypedReference*。我已经对它进行了调整以缩短它,但你可以随心所欲地解决它。将指针的类型更改为,例如void*不会抛出异常。

using System;

unsafe static class Program
{
    static TypedReference* _tr;

    static void Main(string[] args)
    {
        _tr = (TypedReference*) IntPtr.Zero;
    }
}

2 个答案:

答案 0 :(得分:5)

好。这不是一个完整的分析,但我怀疑这是一个足以确定你是否可以做到这一点的目的,即使是错误的IL - 据我所知,你不能。

我也是在查看使用dotPeek的反编译版本时,看不到有关该特定类型/那些特定类型的任何特殊内容,属性方面或其他方面:

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
  {

所以,完成后,我尝试使用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();
        }
    }
}

当你运行它时,抛出一个TypeLoadException,其堆栈跟踪指向System.Reflection.Emit.TypeBuilder.TermCreateClass。然后我用反编译器去了解它,它给了我这个:

[SuppressUnmanagedCodeSecurity]
[SecurityCritical]
[DllImport("QCall", CharSet = CharSet.Unicode)]
private static void TermCreateClass(RuntimeModule module, int tk, ObjectHandleOnStack type);

指向CLR的非托管部分。在这一点上,不要被打败,我挖掘了CLR参考版本的共享源代码。我不会通过我所做的所有跟踪,以避免使这个答案超出所有合理用途,但最终,你最终在/ clr \ src \ _vm \ class.cpp中,在MethodTableBuilder :: SetupMethodTable2函数中(这也似乎设置了字段描述符),你可以在这里找到这些行:

// Mark the special types that have embeded stack poitners in them
                        if (strcmp(name, "ArgIterator") == 0 || strcmp(name, "RuntimeArgumentHandle") == 0)
                            pClass->SetContainsStackPtr();

if (pMT->GetInternalCorElementType() == ELEMENT_TYPE_TYPEDBYREF)
                            pClass->SetContainsStackPtr();

后者与\ src \ inc \ cortypeinfo.h中的信息有关,因此:

// 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)

(它实际上是该列表中唯一的ELEMENT_TYPE_TYPEDBYREF类型。)

然后

然后在各个地方的其他地方使用ContainsStackPtr()以防止使用这些特定类型,包括在字段中 - 来自\ 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);
}

无论如何:为了缩短漫长的长篇故事,看起来似乎是这种类型不可存储的类型有效地硬编码到CLR中,因此如果你想改变列表或提供IL意味着将类型标记为不可存储,您几乎不得不采用Mono或共享源CLR并剥离您自己的版本。

答案 1 :(得分:2)

我在IL中找不到任何表明这些类型有任何特殊之处的东西。我知道编译器有许多特殊情况,例如将int / Int32转换为内部类型int32,或许多与Nullable结构相关的内容。我非常怀疑这些类型也是特殊情况。

一个可能的解决方案是Roslyn,我希望能让你创建这样的约束。