Skip to content

Can we add a way to add attributes without requiring a dependency on their assembly? #99817

Open
@poizan42

Description

@poizan42

The requirement of depending on the assembly an attribute is defined in at runtime is a pretty common annoyance I'm encountering. A common scenario is with model/dto libraries where I want to support both NJ and STJ. If I want to say some fields should be ignored by the serializers then I need to add System.Text.Json.Serialization.JsonIgnoreAttribute and Newtonsoft.Json.JsonIgnoreAttribute. This means the model library suddenly needs a reference to both Newtonsoft.Json and System.Text.Json even though I'm only using them for metadata that's only relevant if a consumer uses one of those libraries.

I think we could use something akin to ELF's weak symbols.

Tentative proposal

As Butler Lampson put it "All problems in computer science can be solved by another level of indirection" - so that is what I'm going to propose here. Add a WeakCustomAttributeReferenceAttribute that embeds the actual attribute.

using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Loader;

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class WeakCustomAttributeReferenceAttribute : Attribute
{
    public string AttributeType { get; }
    public byte[] CustomAttributeDataBlob { get; }
    /// <summary>
    /// Initializes a new instance of the <see cref="WeakCustomAttributeReferenceAttribute"/> class with the specified target attribute type and custom attribute blob.
    /// </summary>
    /// <param name="attributeType">The canonical type name of the embedded attribute.
    /// This is either the Assembly Qualified Name, or the Fully Qualified Name if the type is present in the system library.</param>
    /// <param name="customAttributeDataBlob">A CustomAttrib blob as specified in ECMA-335 Section II.23.3</param>
    public WeakCustomAttributeReferenceAttribute(string attributeType, byte[] customAttributeDataBlob);

    public bool TryGetCustomAttributeData([NotNullWhen(true)] out CustomAttributeData? customAttributeData)
        => TryGetCustomAttributeData(AssemblyLoadContext.Default, out customAttributeData);
    public bool TryGetCustomAttributeData(AssemblyLoadContext assemblyLoadContext, [NotNullWhen(true)] out CustomAttributeData? customAttributeData);
    public bool TryGetCustomAttribute([NotNullWhen(true)] out Attribute? attribute);
    public bool TryGetCustomAttribute<T>([NotNullWhen(true)] out T? attribute)
        where T : Attribute;
}

This would obviously need compiler support so you don't have to create the CustomAttrib blob by hand - also the actual AttributeUsage of the target attribute should be enforced by the compiler. I don't know if it should have methods for inspecting the custom attribute when you don't have the types available. I guess you could use BlobReader or Mono.Cecil for that.

For this to be of much use the behavior of all the GetCustomAttribute(s) methods in System.Reflection should be updated.

  • .GetCustomAttribute(s) and .GetCustomAttributesData will try to get the attribute in WeakCustomAttributeReferenceAttribute attributes. If the type successfully resolves they will act just like if the target attribute had been there directly. On the other hand if the type fails to resolve they will just completely ignore the WeakCustomAttributeReferenceAttribute.
  • Overloads should be added to .GetCustomAttribute(s) and .GetCustomAttributesData that will override this behavior so you can get the WeakCustomAttributeReferenceAttribute instances and inspect them manually.
  • Requesting WeakCustomAttributeReferenceAttribute directly with .GetCustomAttribute() or GetCustomAttributes() overloads taking an attribute type will return the WeakCustomAttributeReferenceAttribute as-is.

Metadata

Metadata

Assignees

Labels

api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Reflectiondesign-discussionOngoing discussion about design without consensus

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions