Description
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()
orGetCustomAttributes()
overloads taking an attribute type will return theWeakCustomAttributeReferenceAttribute
as-is.