自定义属性和GetCustomAttributes的奇怪行为

时间:2014-11-01 05:38:07

标签: c# custom-attributes getcustomattributes

我已经在几个小时内解决了这个问题,而且我找不到与SO相关的任何内容(或google)。

这是我的问题: 我有一个包含对象数组属性的自定义属性。

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class Property : System.Attribute
{
    public object[] Parameters { get; set; }

    public JsonProperty(object[] prms = null)
    {
        Parameters = prms;
    }
}

然后我使用以下代码从属性中读取它:

var customProperties = (Property[])currentProperty.GetCustomAttributes(typeof(Property), false);

这一切都适用于以下情况:

[Property(Parameters = new object[]{}]
<...property...>

但是,如果我将其设置为null ([Property(Parameters = null]),我会收到此错误:

System.Reflection.CustomAttributeFormatException:
'Parameters' property specified was not found.

这是荒谬的,因为属性是在我的自定义属性中定义的。我真的不明白。

所以我的问题是:发生了什么事?

- 编辑

如果我将属性的类型从object []更改为object,则赋值null可以正常工作。

- 编辑以添加代码

属性:

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class JsonProperty : System.Attribute
{
    public object[] Parameters { get; set; }

    public JsonProperty(object[] prms = null)
    {
        Parameters = prms;
    }
}

类别:

public class MyClass
{
    [JsonProperty(Parameters = null)]
    public DateTime Start { get; set; }
}

方法:

public string getAttributes()
{
    Type t = MyClass.GetType();

     // Get only public properties and that have been set.
     var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                       .Where(prop => prop.GetValue(this, null) != null);

     foreach (var prop in properties)
     {
          //The error occur on the next line.
          var jsonProperties =
              (JsonProperty[])prop.GetCustomAttributes(typeof(JsonProperty), false);

- 如果您不理解,请尝试阅读:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/ddebbec6-1653-4502-9802-0b421efec60d/an-unexplicable-customattributeformatexception-from-getcustomattributes?forum=csharpgeneral

我也在那里问过这个问题。

2 个答案:

答案 0 :(得分:0)

我知道旧帖子,但是有一种解决方法。使用反射和自定义属性时,我遇到了类似的问题。我更改了属性以更新设置值(如果它为Null,如下所示)。

public object[] Parameters { get; set; }

已更改为:

private object[] _Parameters = new object[0];

public object[] Parameters {
  get {
    return _Parameters;
  }
  set {
    _Parameters = value ?? new object[0];
  }
}

因此,现在,即使您分配了Null或未分配值,它也可以工作,但是如果希望将属性设置为Null,则希望跳过该属性,则可能需要在其他地方更新逻辑。

在我的情况下,这也是在处理int []而不是object []数组时。

答案 1 :(得分:0)

我在使用 System.Runtime.Serialization.OptionalFieldAttribute 时遇到了同样的错误。要重现它,只需添加一个字段,用属性标记它,然后调用 FieldInfo.GetCustomAttributes()

[OptionalField(VersionAdded = 0)]
private int _testA = 0;
private void SomeMethod()
{
    FieldInfo fieldInfo = GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.Name == "_testA").FirstOrDefault();

    fieldInfo.GetCustomAttributes();
    //You will get a CustomAttributeFormatException('OptionalField' property specified was not found) for VersionAdded values less then 1.
}

好问题是为什么!正如我所见,我们可以从 GetCustomAttributes() 的代码中得到答案,其中有一个 try{}catch{},它可以捕获所有错误并抛出 CustomAttributeFormatException,所以我认为在我的情况下,错误来自 OptionalFieldAttribute 的 setter:

[AttributeUsage(AttributeTargets.Field, Inherited=false)]
[System.Runtime.InteropServices.ComVisible(true)]
public sealed class OptionalFieldAttribute : Attribute 
{
    int versionAdded = 1;
    public OptionalFieldAttribute() { }
    
    public int VersionAdded 
    {
        get {
            return this.versionAdded;
        }
        set {
            if (value < 1)
                throw new ArgumentException(Environment.GetResourceString("Serialization_OptionalFieldVersionValue"));
            Contract.EndContractBlock();
            this.versionAdded = value;
        }
    }
}


[System.Security.SecurityCritical]
private unsafe static object[] GetCustomAttributes(
    RuntimeModule decoratedModule, int decoratedMetadataToken, int pcaCount, 
    RuntimeType attributeFilterType, bool mustBeInheritable, IList derivedAttributes, bool isDecoratedTargetSecurityTransparent)
{
    if (decoratedModule.Assembly.ReflectionOnly)
        throw new InvalidOperationException(Environment.GetResourceString("Arg_ReflectionOnlyCA"));
    Contract.EndContractBlock();

    MetadataImport scope = decoratedModule.MetadataImport;
    CustomAttributeRecord[] car = CustomAttributeData.GetCustomAttributeRecords(decoratedModule, decoratedMetadataToken);

    bool useObjectArray = (attributeFilterType == null || attributeFilterType.IsValueType || attributeFilterType.ContainsGenericParameters);
    Type arrayType = useObjectArray ? typeof(object) : attributeFilterType;

    if (attributeFilterType == null && car.Length == 0)
        return CreateAttributeArrayHelper(arrayType, 0);

    object[] attributes = CreateAttributeArrayHelper(arrayType, car.Length);
    int cAttributes = 0;

    // Custom attribute security checks are done with respect to the assembly *decorated* with the 
    // custom attribute as opposed to the *caller of GetCustomAttributes*.
    // Since this assembly might not be on the stack and the attribute ctor or property setters we're about to invoke may
    // make security demands, we push a frame on the stack as a proxy for the decorated assembly (this frame will be picked
    // up an interpreted by the security stackwalker).
    // Once we push the frame it will be automatically popped in the event of an exception, so no need to use CERs or the
    // like.
    SecurityContextFrame frame = new SecurityContextFrame();
    frame.Push(decoratedModule.GetRuntimeAssembly());

    // Optimization for the case where attributes decorate entities in the same assembly in which case 
    // we can cache the successful APTCA check between the decorated and the declared assembly.
    Assembly lastAptcaOkAssembly = null;

    for (int i = 0; i < car.Length; i++)
    {
        object attribute = null;
        CustomAttributeRecord caRecord = car[i];

        IRuntimeMethodInfo ctor = null;
        RuntimeType attributeType = null;
        bool ctorHasParameters, isVarArg;
        int cNamedArgs = 0;

        IntPtr blobStart = caRecord.blob.Signature;
        IntPtr blobEnd = (IntPtr)((byte*)blobStart + caRecord.blob.Length);
        int blobLen = (int)((byte*)blobEnd - (byte*)blobStart);

        if (!FilterCustomAttributeRecord(caRecord, scope, ref lastAptcaOkAssembly, 
                                         decoratedModule, decoratedMetadataToken, attributeFilterType, mustBeInheritable, 
                                         attributes, derivedAttributes,
                                         out attributeType, out ctor, out ctorHasParameters, out isVarArg))
            continue;

        if (ctor != null)
        {
            // Linktime demand checks 
            // decoratedMetadataToken needed as it may be "transparent" in which case we do a full stack walk
            RuntimeMethodHandle.CheckLinktimeDemands(ctor, decoratedModule, isDecoratedTargetSecurityTransparent);
        }
        else
        {
            // 


        }

        // Leverage RuntimeConstructorInfo standard .ctor verfication
        RuntimeConstructorInfo.CheckCanCreateInstance(attributeType, isVarArg); 

        // Create custom attribute object
        if (ctorHasParameters)
        {
            attribute = CreateCaObject(decoratedModule, ctor, ref blobStart, blobEnd, out cNamedArgs); 
        }
        else
        {
            attribute = RuntimeTypeHandle.CreateCaInstance(attributeType, ctor);

            // It is allowed by the ECMA spec to have an empty signature blob
            if (blobLen == 0)
                cNamedArgs = 0;
            else
            {
                // Metadata is always written in little-endian format. Must account for this on
                // big-endian platforms.
#if BIGENDIAN
                const int CustomAttributeVersion = 0x0100;
#else
                const int CustomAttributeVersion = 0x0001;
#endif
                if (Marshal.ReadInt16(blobStart) != CustomAttributeVersion)
                    throw new CustomAttributeFormatException();
                blobStart = (IntPtr)((byte*)blobStart + 2); // skip version prefix

                cNamedArgs = Marshal.ReadInt16(blobStart);
                blobStart = (IntPtr)((byte*)blobStart + 2); // skip namedArgs count
#if BIGENDIAN
                cNamedArgs = ((cNamedArgs & 0xff00) >> 8) | ((cNamedArgs & 0x00ff) << 8);
#endif
            }
        }

        for (int j = 0; j < cNamedArgs; j++)
        {
            #region // Initialize named properties and fields
            string name;
            bool isProperty;
            RuntimeType type;
            object value;
            
            IntPtr blobItr = caRecord.blob.Signature;

            GetPropertyOrFieldData(decoratedModule, ref blobStart, blobEnd, out name, out isProperty, out type, out value);

            try
            {
                if (isProperty)
                {
                    #region // Initialize property
                    if (type == null && value != null)
                    {
                        type = (RuntimeType)value.GetType();
                        if (type == Type_RuntimeType)
                            type = Type_Type;
                    }

                    RuntimePropertyInfo property = null;

                    if (type == null)
                        property = attributeType.GetProperty(name) as RuntimePropertyInfo;
                    else
                        property = attributeType.GetProperty(name, type, Type.EmptyTypes) as RuntimePropertyInfo;

                    // Did we get a valid property reference?
                    if (property == null)
                    {
                        throw new CustomAttributeFormatException(
                            String.Format(CultureInfo.CurrentUICulture, Environment.GetResourceString(
                                isProperty ? "RFLCT.InvalidPropFail" : "RFLCT.InvalidFieldFail"), name));
                    }

                    RuntimeMethodInfo setMethod = property.GetSetMethod(true) as RuntimeMethodInfo;
                    
                    // Public properties may have non-public setter methods
                    if (!setMethod.IsPublic)
                        continue;

                    RuntimeMethodHandle.CheckLinktimeDemands(setMethod, decoratedModule, isDecoratedTargetSecurityTransparent);

                    setMethod.UnsafeInvoke(attribute, BindingFlags.Default, null, new object[] { value }, null);
                    #endregion
                }
                else
                {
                    RtFieldInfo field = attributeType.GetField(name) as RtFieldInfo;

                    if (isDecoratedTargetSecurityTransparent)
                    {
                        RuntimeFieldHandle.CheckAttributeAccess(field.FieldHandle, decoratedModule.GetNativeHandle());
                    }

                    field.CheckConsistency(attribute);
                    field.UnsafeSetValue(attribute, value, BindingFlags.Default, Type.DefaultBinder, null);
                }
            }
            catch (Exception e)
            {
                throw new CustomAttributeFormatException(
                    String.Format(CultureInfo.CurrentUICulture, Environment.GetResourceString(
                        isProperty ? "RFLCT.InvalidPropFail" : "RFLCT.InvalidFieldFail"), name), e);
            }
            #endregion
        }

        if (!blobStart.Equals(blobEnd))
            throw new CustomAttributeFormatException();

        attributes[cAttributes++] = attribute;
    }

    // The frame will be popped automatically if we take an exception any time after we pushed it. So no need of a catch or
    // finally or CERs here.
    frame.Pop();

    if (cAttributes == car.Length && pcaCount == 0)
        return attributes;

    object[] result = CreateAttributeArrayHelper(arrayType, cAttributes + pcaCount);
    Array.Copy(attributes, 0, result, 0, cAttributes);
    return result;
}

异常消息绝对没有帮助,因为它没有给出根本原因!