制作类型';s实例不可存储

本文关键字:实例 存储 类型 | 更新日期: 2023-09-27 17:54:05

  • 有没有一种方法可以标记一个类型(甚至更好的方法是,一个接口(,使其实例不能存储在字段中(类似于TypedReferenceArgIterator(
  • 同样,有没有一种方法可以防止实例通过匿名方法传递,以及——通常——模仿上面两种类型的行为
  • 这可以通过ILDasm或更广泛地通过IL编辑来实现吗?由于UnconstraintedMelody通过对编译的程序集进行二进制编辑来获得通常无法获得的结果,因此可能有一种方法可以通过相同的方法"标记"某些类型(甚至更好的是,抽象类型或标记接口(

我怀疑它在编译器中是硬编码的,因为错误CS0610的文档中写道:

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

在我看来,这暗示了像这样的类型集是可以扩展的——但我可能错了。

我在SO上搜索了一些,虽然我知道以编程方式抛出编译器错误是不可能的,但我找不到任何来源表明某些"特殊"类型的行为无法复制。

即使这个问题主要是学术性的,答案也可能有一些用法。例如,有时确定某个对象的生存期被限制在创建它的方法块中是有用的

EDIT:RuntimeArgumentHandle是另一种(未提及(不可存储类型。

EDIT 2:如果它有任何用处,CLR似乎也以不同的方式处理这些类型,如果不仅仅是编译器(仍然假设这些类型与其他类型没有任何不同(。例如,下面的程序将抛出一个关于TypedReference*TypeLoadException。我把它改短了,但你可以随心所欲地修改它。将指针的类型更改为void*不会引发异常。

using System;
unsafe static class Program
{
    static TypedReference* _tr;
    static void Main(string[] args)
    {
        _tr = (TypedReference*) IntPtr.Zero;
    }
}

制作类型';s实例不可存储

好的。这不是一个完全的分析,但我怀疑它足以确定你是否能做到这一点,即使是通过摆弄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((在其他地方被使用,以防止这些特定类型被使用,包括在字段-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);
}

总之:长话短说,以这种方式不可存储的类型似乎被有效地硬编码到CLR中,因此,如果你想更改列表或提供一种IL方法来将类型标记为不可存储,你几乎必须使用Mono或共享源CLR,并衍生出你自己的版本。

我在IL中找不到任何表明这些类型有任何特殊性的东西。我知道编译器有很多特殊情况,比如将int/Int32转换为内部类型int32,或者许多与Nullable结构有关的事情。我高度怀疑这些类型也是特殊情况。

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