8
  • 有没有办法标记一个类型(或者更好的是,一个接口),这样它的实例就不能存储在一个字段中(以类似于TypedReferenceand的方式ArgIterator)?
  • 同样,有没有办法防止实例通过匿名方法传递,并且——通常——模仿上述两种类型的行为?
  • 这可以通过 ILDasm 或更一般地通过 IL 编辑来完成吗?由于UnconstrainedMelody通过编译程序集的二进制编辑实现了通常无法获得的结果,也许有一种方法可以通过相同的方法“标记”某些类型(或者甚至更好,抽象类型或标记接口)。

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

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

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

我在 SO 上进行了一些搜索,虽然我知道无法以编程方式引发编译器错误,但我找不到任何来源说明某些“特殊”类型的行为无法复制。

即使这个问题主要是学术性的,也可能有一些答案的用法。例如,有时确保某个对象的生命周期受限于创建它的方法块可能很有用。

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

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

using System;

unsafe static class Program
{
    static TypedReference* _tr;

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

2 回答 2

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 参考版本的共享源。我不会进行我所做的所有跟踪,以避免使这个答案超出所有合理使用范围,但最终你会在 MethodTableBuilder::SetupMethodTable2 函数中进入 \clr\src\vm\class.cpp (这似乎也设置了字段描述符),您可以在其中找到这些行:

// 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 并衍生出您自己的版本。

于 2013-01-19T16:16:21.363 回答
2

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

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

于 2013-01-16T16:26:50.100 回答