Open sandboxFocusImprove this doc

LA0030: Introduced Custom Attribute Has a Value That Cannot Be Stored

Severity Error
Reported by PostSharp
Message Cannot introduce the custom attribute <AttributeType> on <DeclarationKind> <Declaration>: the value of <Argument> cannot be stored in a custom attribute: <Reason>.

Cause

A CustomAttributeIntroductionAspect adds a custom attribute to its target declaration. It describes the attribute with an ObjectConstruction: the constructor, the constructor arguments, and the named arguments that set fields or properties. PostSharp writes these values into the metadata of the assembly, as the C# compiler does for an attribute in the source code.

Custom attribute metadata can only store the values that C# accepts in an attribute: primitive types, strings, enumerations, Type, and one-dimensional arrays of these types. When a parameter, a field or a property of the attribute has the type object, the value must still be of one of these types. ObjectConstruction checks only that the value is assignable to the parameter, so a value such as a TimeSpan, a DateTime or a decimal reaches the build and PostSharp reports LA0030.

<Argument> says which value fails: constructor argument and its position, or Property or Field and its name. <Reason> is the explanation of the serializer of custom attributes; for a type that cannot be stored, it starts with Invalid image, although the assembly is valid. PostSharp reports the message on the declaration that receives the attribute, not on the code of the aspect.

In the following example, the [DefaultTimeout] aspect adds [DefaultValue] to the Timeout property, and passes a TimeSpan to the constructor of DefaultValueAttribute that takes an object.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;
using PostSharp.Serialization;

namespace Errors.LA0030_Error;

// Adds [DefaultValue] with a default timeout to every TimeSpan property of the target class.
[PSerializable]
[MulticastAttributeUsage( MulticastTargets.Class )]
public class DefaultTimeoutAttribute : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
    {
        foreach ( PropertyInfo property in ((Type) targetElement).GetProperties() )
        {
            if ( property.PropertyType == typeof(TimeSpan) )
            {
                // A TimeSpan cannot be stored in a custom attribute.
                ObjectConstruction defaultValue = new(
                    typeof(DefaultValueAttribute).GetConstructor( new[] { typeof(object) } )!,
                    TimeSpan.FromSeconds( 30 ) );

                yield return new AspectInstance( property, new CustomAttributeIntroductionAspect( defaultValue ) );
            }
        }
    }
}

[DefaultTimeout]
public class ServiceSettings
{
    public string Endpoint { get; set; } = "https://example.com/api";

    // The aspect introduces [DefaultValue] on this property.
    public TimeSpan Timeout { get; set; }
}

public static class Program
{
    public static void Main()
    {
        DefaultValueAttribute? attribute = typeof(ServiceSettings).GetProperty( nameof(ServiceSettings.Timeout) )!
            .GetCustomAttribute<DefaultValueAttribute>();

        Console.WriteLine( $"Timeout defaults to {attribute?.Value}." );
    }
}
error LA0030: LA0030_Error.cs(40,21): Cannot introduce the custom attribute System.ComponentModel.DefaultValueAttribute on Property Errors.LA0030_Error.ServiceSettings.Timeout: the value of constructor argument 0 cannot be stored in a custom attribute: Invalid image. Cannot map this type signature to a serialization type: System.TimeSpan. (Parameter 'typeSignature').

Resolution

  • Pass the value in a form that custom attribute metadata can store, and use the constructor of the attribute that converts it. For example, DefaultValueAttribute has a constructor that takes a Type and a string.
  • When you own the attribute class, add a constructor or a property of a supported type, for example the number of seconds as an int, and convert the value in the attribute.

In the following example, the timeout is passed as typeof(TimeSpan) and the string "00:00:30", which DefaultValueAttribute converts to a TimeSpan when the program reads the attribute.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;
using PostSharp.Serialization;

namespace Errors.LA0030_Fixed;

// Adds [DefaultValue] with a default timeout to every TimeSpan property of the target class.
[PSerializable]
[MulticastAttributeUsage( MulticastTargets.Class )]
public class DefaultTimeoutAttribute : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
    {
        foreach ( PropertyInfo property in ((Type) targetElement).GetProperties() )
        {
            if ( property.PropertyType == typeof(TimeSpan) )
            {
                // The TimeSpan is passed as a Type and a string, which a custom attribute can store.
                ObjectConstruction defaultValue = new(
                    typeof(DefaultValueAttribute).GetConstructor( new[] { typeof(Type), typeof(string) } )!,
                    typeof(TimeSpan),
                    "00:00:30" );

                yield return new AspectInstance( property, new CustomAttributeIntroductionAspect( defaultValue ) );
            }
        }
    }
}

[DefaultTimeout]
public class ServiceSettings
{
    public string Endpoint { get; set; } = "https://example.com/api";

    public TimeSpan Timeout { get; set; }
}

public static class Program
{
    public static void Main()
    {
        DefaultValueAttribute? attribute = typeof(ServiceSettings).GetProperty( nameof(ServiceSettings.Timeout) )!
            .GetCustomAttribute<DefaultValueAttribute>();

        Console.WriteLine( $"Timeout defaults to {attribute?.Value}." );
    }
}
Timeout defaults to 00:00:30.

See Also

Reference

CustomAttributeIntroductionAspect
ObjectConstruction

Other Resources

Introducing Custom Attributes
Adding Aspects Programmatically Using IAspectProvider