制作类型';s实例不可存储
本文关键字:实例 存储 类型 | 更新日期: 2023-09-27 17:54:05
- 有没有一种方法可以标记一个类型(甚至更好的方法是,一个接口(,使其实例不能存储在字段中(类似于
TypedReference
和ArgIterator
( - 同样,有没有一种方法可以防止实例通过匿名方法传递,以及——通常——模仿上面两种类型的行为
- 这可以通过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;
}
}
好的。这不是一个完全的分析,但我怀疑它足以确定你是否能做到这一点,即使是通过摆弄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,我希望它能让您创建这样的约束。