1

C# 11 has introduced generic attributes. Is it possible to get the attributes that are attached to a PropertyInfo using the GetCustomAttribute method (or similar) without knowing it's generic type, particularly where the attribute inherits from another?

Here's what I have:

public class MyTypedAttribute<TValue> : Attribute
{ 
    public virtual TValue ProcessValue(object value) { }
}

public class MyNumberAttribute : MyTypedAttribute<int>
{ 
    public override int ProcessValue(object value) { }
}

public class MyStringAttribute : MyTypedAttribute<string>
{ 
    public override string ProcessValue(object value) { }
}

My class has properties with a range of these attributes on them, and I want to get them using reflection. I would normally use the generic PropertyInfo.GetCustomAttribute<> function, but this doesnt seem to work when the attributes themselves also expect a generic parameter (unless the generic type is known at compile time, which in my case they it's not)

PropertyInfo myProp = ...;
var attr = myProp.GetCustomAttribute<MyTypedAttribute<>>();

Compile error: Unexpected use of an unbound generic name

It seems I can use the non-generic equivalent function, but the returned attribute is of type Attribute?, and it seems I cannot cast this without knowing the generic type. We cannot specify empty angle brackets when casting, so a type must be specified, but I dont know what the type is. The only thing I can think of doing is using object as the generic type, but this fails at run time:

var attr = (MyTypedAttribute<object>?)myProp.GetCustomAttribute(typeof(MyTypedAttribute<>))

Runtime error: Unable to cast object of type 'MyNumberAttribute' to type 'MyTypedAttribute`1[System.Object]'.'

Essentially what I'm trying to do is get the attributes and call the ProcessValue method, capturing the returned value. Is there any way to do this?

devklick
  • 2,000
  • 3
  • 30
  • 47
  • Why do you want to cast it? it does not make sense to cast them when you are getting the reflection!!! What do you want to do with them after finding them?! – Hassan Monjezi Jun 15 '23 at 20:56
  • @HassanMonjezi The base class has a virtual method that uses the generic type, which the derived classes override. I want to call this method and capture the returned value. I'll update the question to reflect this. – devklick Jun 15 '23 at 21:01
  • 2
    Reflection is like a virus that removes type safety. Once you start using Reflection, you often have to give up on type safety all together. `var` is a compile time reflection of the compile time type assigned to it, so your `attr` can only have a single compile time type. – NetMage Jun 15 '23 at 21:12
  • 1
    You can get the attribute with reflection. You can even call `ProcessValue` with reflection. But the result of such a call will still be `object` (since reflection can't know at compile-time which method of which signature it will call). So, you have to decide what to do with this non-typed value. – Serg Jun 15 '23 at 21:44
  • 2
    Besides not getting much value from the generic type in this case as others have mentioned, it feels like you may be misusing attributes in a more general sense. It's uncommon and usually unwise to put business logic (methods like ProcessValue) on attributes directly. What happens when you end up needing another class in order to process values? Dependency injection and unit testability goes out the window. Consider using attributes to do nothing but annotate your class with metadata, and set up some kind of mapping from those annotations to specific behaviors that exist separately. – StriplingWarrior Jun 15 '23 at 22:00
  • @StriplingWarrior I agree to a point, and I think my poorly named example method `ProcessValue` makes it seem like I wanted to do a lot in this method, which isnt the case. My implementation can be loosely compared to the `ValidationAttribute`s and their [`IsValid`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationattribute.isvalid) method. Dependency injection doesnt exactly go out the window - dependencies can be injected directly into the method (albeit without setting up a DI container). From this point of view, testability isn't really affected. – devklick Jun 16 '23 at 08:57

1 Answers1

1

You can make a non-generic base class

public class MyTypedAttribute<TValue> : MyUntypedAttribute
{ 
    public virtual TValue ProcessValue(object value)
    {
        // whatever
    }

    public override object ProcessValueObject(object value) =>
        ProcessValue(value);
}

public class MyUntypedAttribute : Attribute
{ 
    public abstract object ProcessValueObject(object value);
}

Then you can do

var attr = myProp.GetCustomAttribute<MyUntypedAttribute>();
Charlieface
  • 52,284
  • 6
  • 19
  • 43