获取具有多个枚举数组的属性构造函数参数时出现异常

时间:2016-09-15 12:51:14

标签: c# .net system.reflection

当我发现一个奇怪的案例时,我正在玩属性和反射。当我尝试获取自定义属性的构造函数参数时,下面的代码在运行时给了我一个异常。

using System;
using System.Reflection;

class Program
{
    [Test(new[] { Test.Foo }, null)]
    static void Main(string[] args)
    {
        var type = typeof(Program);
        var method = type.GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic);
        var attribute = method.GetCustomAttributesData()[0].ConstructorArguments;

        Console.ReadKey();
    }
}

public enum Test
{
    Foo,
    Bar
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestAttribute : Attribute
{
    public TestAttribute(Test[] valuesOne, Test[] valuesTwo)
    {
    }
}

问题似乎是传递给Test属性构造函数的参数。如果其中一个为null,则ConstructorArguments抛出异常。例外情况为ArgumentExceptionname为异常消息。

以下是来自ConstructorArguments电话的堆栈跟踪:

System.RuntimeTypeHandle.GetTypeByNameUsingCARules(String name, RuntimeModule scope)
System.Reflection.CustomAttributeTypedArgument.ResolveType(RuntimeModule scope, String typeName)
System.Reflection.CustomAttributeTypedArgument..ctor(RuntimeModule scope, CustomAttributeEncodedArgument encodedArg)
System.Reflection.CustomAttributeData.get_ConstructorArguments()

如果我为每个参数设置一个非空值,则没有异常。它似乎只发生在枚举数组中。如果我添加另一个参数如string并将它们设置为null,则没有问题。

解决方案可能是总是传递一个值,例如一个空数组,但在这里我想保留传递null值的能力,因为它在我的情况下具有特殊含义。

3 个答案:

答案 0 :(得分:2)

这与指定structure of the blob where the custom attribut e有关。

  

数组值以指示元素数量的整数开头   在数组中,然后项目值连接在一起。

     

使用长度-1表示空数组。

     

使用字节0x55 后跟表示枚举参数   字符串指定名称和枚举类型的汇编。

不幸的是,如果将枚举数组作为null传递,则枚举名称将丢失。

就本机调试而言,这是相关的源代码

    else if (encodedType == CustomAttributeEncoding.Array)
    {                
        encodedType = encodedArg.CustomAttributeType.EncodedArrayType;
        Type elementType;

        if (encodedType == CustomAttributeEncoding.Enum)
        {
            elementType = ResolveType(scope, encodedArg.CustomAttributeType.EnumName);
        }

这就是c.tor参数的实例化方式

        for (int i = 0; i < parameters.Length; i++)
            m_ctorParams[i] = new CustomAttributeCtorParameter(InitCustomAttributeType((RuntimeType)parameters[i].ParameterType));

问题是枚举值只是使用基础值(基本上是int)来表示: CLR实现(RuntimeType)必须查看属性构造函数签名来解释它,但自定义属性签名与编码的其他类型的签名有很大不同在.NET程序集中。

更具体地说,没有定义的encodedArrayType(来自GetElementType),以下if变为false(并且enumName保持为null)

        if (encodedType == CustomAttributeEncoding.Array)
        {
            parameterType = (RuntimeType)parameterType.GetElementType();
            encodedArrayType = CustomAttributeData.TypeToCustomAttributeEncoding(parameterType);
        }

        if (encodedType == CustomAttributeEncoding.Enum || encodedArrayType == CustomAttributeEncoding.Enum)
        {
            encodedEnumType = TypeToCustomAttributeEncoding((RuntimeType)Enum.GetUnderlyingType(parameterType));
            enumName = parameterType.AssemblyQualifiedName;
        }

ILDASM

您可以在ildasm

中找到Main的.custom实例

的情况

[Test(new[] { Test.Bar }, null)]
static void Main(string[] args)

它是(注意FF FF FF FF表示 -1 数组大小

.custom instance void TestAttribute::.ctor(valuetype Test[],
                                         valuetype Test[]) = 
( 01 00 01 00 00 00 01 00 00 00 FF FF FF FF 00 00 ) 

[Test(new[] { Test.Bar }, new Test[] { })]
static void Main(string[] args)

你看

.custom instance void TestAttribute::.ctor(valuetype Test[],
                                          valuetype Test[]) = 
( 01 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 ) 

CLR虚拟机

最后,您确认CLR virtual machine仅在大小不同于-1时将自定义属性的blob读入数组

case SERIALIZATION_TYPE_SZARRAY:      
typeArray:
{
    // read size
    BOOL isObject = FALSE;
    int size = (int)GetDataFromBlob(pCtorAssembly, SERIALIZATION_TYPE_I4, nullTH, pBlob, endBlob, pModule, &isObject);
    _ASSERTE(!isObject);

    if (size != -1) {
        CorSerializationType arrayType;
        if (th.IsEnum()) 
            arrayType = SERIALIZATION_TYPE_ENUM;
        else
            arrayType = (CorSerializationType)th.GetInternalCorElementType();

        BASEARRAYREF array = NULL;
        GCPROTECT_BEGIN(array);
        ReadArray(pCtorAssembly, arrayType, size, th, pBlob, endBlob, pModule, &array);
        retValue = ObjToArgSlot(array);
        GCPROTECT_END();
    }
    *bObjectCreated = TRUE;
    break;
}

总之,在这种情况下,contructor参数不会在C#中实例化,因此只能从构造函数本身检索 :实际上是创建了自定义属性(通过CreateCaObject )在CLR虚拟机中通过使用不安全指针调用其构造函数(直接到 blob

    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static unsafe extern Object _CreateCaObject(RuntimeModule pModule, IRuntimeMethodInfo pCtor, byte** ppBlob, byte* pEndBlob, int* pcNamedArgs);
    [System.Security.SecurityCritical]  // auto-generated
    private static unsafe Object CreateCaObject(RuntimeModule module, IRuntimeMethodInfo ctor, ref IntPtr blob, IntPtr blobEnd, out int namedArgs)
    {
        byte* pBlob = (byte*)blob;
        byte* pBlobEnd = (byte*)blobEnd;
        int cNamedArgs; 
        object ca = _CreateCaObject(module, ctor, &pBlob, pBlobEnd, &cNamedArgs);
        blob = (IntPtr)pBlob;
        namedArgs = cNamedArgs;
        return ca;
    }

可能的错误

可能的错误的关键点是

            unsafe
            {
                ParseAttributeArguments(
                    attributeBlob.Signature,
                    (int)attributeBlob.Length,
                    ref customAttributeCtorParameters,
                    ref customAttributeNamedParameters,
                    (RuntimeAssembly)customAttributeModule.Assembly);
            }

中实施
FCIMPL5(VOID, Attribute::ParseAttributeArguments, void* pCa, INT32 cCa,
        CaArgArrayREF* ppCustomAttributeArguments,
        CaNamedArgArrayREF* ppCustomAttributeNamedArguments,
        AssemblyBaseObject* pAssemblyUNSAFE)

可能会审查以下内容......

    cArgs = (*ppCustomAttributeArguments)->GetNumComponents();

    if (cArgs)
    {        
        gc.pArgs = (*ppCustomAttributeArguments)->GetDirectPointerToNonObjectElements();

提议修复

您可以使用github中的建议FIX将此问题重新发送到CoreCLR。

答案 1 :(得分:1)

在我之前的回答中,我跟踪了当前mscorlib的标准.Net代码如何丢失Enum名称...以及此异常的原因

现在我只希望根据您的特定测试枚举定义显示构造函数参数的具体自定义重新设计(因此以下不够标准,不能作为实际改进提出,但它只是解释的补充部分)

var dataCust = method.GetCustomAttributesData()[0];
var ctorParams = dataCust.GetType().GetField("m_ctorParams", BindingFlags.Instance | BindingFlags.NonPublic);
var reflParams = ctorParams.GetValue(dataCust);

var results = new List<Test[]>();
bool a = reflParams.GetType().IsArray;
if (a)
{
    var mya = reflParams as Array;
    for (int i = 0; i < mya.Length; i++)
    {
        object o = mya.GetValue(i);
        ctorParams = o.GetType().GetField("m_encodedArgument", BindingFlags.Instance | BindingFlags.NonPublic);
        reflParams = ctorParams.GetValue(o);
        var array = reflParams.GetType().GetProperty("ArrayValue", BindingFlags.Instance | BindingFlags.Public);
        reflParams = array.GetValue(reflParams);

        if (reflParams != null)
        {
            var internal_array = reflParams as Array;
            var resultTest = new List<Test>();
            foreach (object item in internal_array)
            {
                ctorParams = item.GetType().GetField("m_primitiveValue", BindingFlags.Instance | BindingFlags.NonPublic);
                reflParams = ctorParams.GetValue(item);
                resultTest.Add((Test)long.Parse(reflParams.ToString()));
            }
            results.Add(resultTest.ToArray());
        } else
        {
            results.Add(null);
        } 

    }
}

所以results将包含构造函数中使用的Test[]参数列表。

答案 2 :(得分:0)

我怀疑这是一个.NET错误!

但是如果你需要一个解决方法,你可以将构造函数args复制到成员并访问method.GetCustomAttribute<TestAttribute>().valuesOne等。