-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Champion "Allow Generic Attributes" #124
Comments
I've wanted this for a long time! |
I would be happpy to have this, too! |
Very compelling. Here's my use case, one among many:
Usage:
Short in time now, sorry for the poor pseudo, but I'm sure I'm understood. |
@weitzhandler Your example is pretty flawed since lambdas can't be passed as an attribute parameter; only primitives, A more sensible example of where a generic attribute would be useful: // This concerns a bot command system that can parse strings into typed arguments,
// but sometimes the "default" reader for something may be too narrow,
// so this on a parameter would override which class does the reading for that argument.
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class OverrideTypeReaderAttribute<TReader> : Attribute
where TReader : TypeReader
{
// We're only interested in the Type info at this point,
// but we would really like the type-safety of the generic constraint
public Type TypeReaderType { get; }
public OverrideTypeReaderAttribute()
{
TypeReaderType = typeof(TReader);
}
}
// Usage:
[Command("foo")]
public Task Foo([OverrideTypeReader<AlternativeBoolReader>] bool switch)
{
//.....
} Currently we imitate the constraint by taking a |
@Joe4evr |
I have not seen this proposal before, not to mention that such a thing would require quite a hefty spec change since as it stands, the compiler needs to be able to serialize the arguments directly in the metadata. It is possible, if wanted for whatever reason, to instantiate an attribute as just a normal class, and that could use any type of parameter: var attr = new SomethingSpecialAttribute(GetSomeData()); But that should only be a secondary constructor, otherwise it's totally useless to have that class be an attribute in the first place. (I have an example of where this is used in the same codebase I described above, but that's getting off-topic.) |
No. |
I suppose that's on the use-site, whereas an abstract generic attribute doesn't seem to be harmful, abstract class SomeAttribute<T> : Attritbue {} Could we permit the above without (any) runtime support? |
Would this feature support this?
|
@ymassad According to the proposal, your example is correct but possibly not for the reasons that you expect. Here is an example that might make things a little clearer. [SomeAttribute<A>]
public class GenericClass<A>
{
[SomeAttribute<B>]
public void DoSomething(B input) { }
}
public class SomeAttribute<C> : Attribute
{
// `C` is either `A` or `B` according to its target.
} As you can see from the example, the generic context comes from the attributes target. |
@roydukkey , I am not sure I understand. Can you elaborate? Is B a concrete type? |
@ymassad Sorry. I meant to write this. [SomeAttribute<B>]
public void DoSomething<B>(B input) { } I believe this is the spec. |
The attribute can't have open generic type it must be instantiated at compile time |
@ymassad What attribute instance would you expect |
@jnm2 , Thanks. I see now why this is not possible. what about this:
|
@ymassad The problem there (if I remember the metadata format correctly) is that attribute arguments of type System.Type are stored as serialized strings containing the assembly-qualified name. What string can be deserialized (similar to Edit: |
Yep, https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf, section II.23.3 Custom attributes, page 268:
|
@jnm2 , thanks. |
Right, if you want to use generic type constraints then you'll need to define multiple versions of that attribute, one for each number of generic type arguments you can accept: One can argue that variadic generics would make your use case easier, but it gets substantially more complicated for any use case that would want to use those generic type arguments for anything other than reflection purposes. |
@HaloFour this would work very well with variadic generic paramaters. |
We will make this feature available in the "preview" language version in .NET 6 RC1. We found that a large number of runtimes and tools (Mono, C++/CLI, etc.) may still have issues consuming generic attributes, so we are going to take an additional release cycle to try and iron out the issues, and give ourselves some time to make breaking changes with runtime APIs. For example, if a user calls using System;
// what should the value of 'attrs' be here?
var attrs = CustomAttributeData.GetCustomAttributes(typeof(C), typeof(Attr<>));
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
class Attr<T> : Attribute { }
[Attr<int>]
[Attr<string>]
class C { } |
It’s a bit cheeky, but why not Attribute[] attrs = //…
attrs[0].GetType() // => typeof(Attr<int>) from there you can get what you need with reflection. practically I’d probably, inherit from a non generic base type that exposed the generic types of as for allow multiple, I think you’ve got to treat each different generic as a different class, the same way you would static properties in a generic class. I’d support adding an attribute usage for |
I have a feeling that's going to be the majority case anyway.
Looking at the existing overloads for [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
class Attr<T1, T2> : Attribute { }
[Attr<long, int>]
[Attr<long, string>]
[Attr<int, string>]
[Attr<object, string>]
class C { }
// { Attr<long, int>, Attr<long, string>, Attr<int, string>, Attr<object, string> }
var attrs0 = CustomAttributeData.GetCustomAttributes(typeof(C), typeof(Attr<,>));
// { Attr<long, int>, Attr<long, string> }
var attrs1 = CustomAttributeData.GetCustomAttributes(typeof(C), typeof(Attr<long,>));
// { Attr<long, string>, Attr<int, string>, Attr<object, string> }
var attrs2 = CustomAttributeData.GetCustomAttributes(typeof(C), typeof(Attr<,string>));
// { } (none of the applied attributes have 'object' as T2)
var attrs3 = CustomAttributeData.GetCustomAttributes(typeof(C), typeof(Attr<,object>)); Does that make sense? I think it may be beneficial to add a new |
The catch here is that you cannot partially bind a generic type in a
Now, onto the real stuff:
Those arguments are enough to show that an open generic attribute type can function as a wildcard to return all attributes that are of the type. The other option is to discard that option and throw an exception. I see no reason to invalidate functionality that cannot be otherwise utilized. It should however be properly marked down in the documentation that open generic attribute types result in all attribute instances binding to the specified generic type. |
I totally +1 the idea of allowing partial generic binding to be a thing, but why stop at
@alfasgd Is there some technical reason behind this (beyond the obvious)? I understand that in most cases the generic type would need to be interacted with (so not talking about trivial use of More than this just being convenient, the Let's take an example. Object table = new Dictionary<string, bool> { ["e"] = true };
if (table is Dictionary<string,> referenceTable) {
Console.WriteLine(referenceTable.ContainsKey("e"));
}
if (table is Dictionary<,bool> referenceTable) {
Console.WriteLine(referenceTable.Values[0]);
} With that, it would seem reasonable to then also enable similar access to static members, although I recognize it is a little different. static class TestClassA<T> {
string testA = "";
}
...
TestClassA<>.testA = ...; And I mean, while I'm at it, may as well point out this one too. (Not open generic type, but still a partial generic) public static TNotInferrable TestA<TInferrableA, TInferrableB, TNotInferrable>(TInferrableA inferrableA, TInferrableB inferrableB) => ...;
public static void TestB<TInferrableA, TInferrableB>(TInferrableA inferrableA, TInferrableB inferrableB) => ...;
...
var success = TestB<,,bool>("", 341.56D);
TestA("", 341.56D); // currently legal Basically if you're going to relax restrictions on generic syntax to allow constraining by specific parameters for the sake of convenience, may as well apply that wherever possible. Just some thoughts. Sorry for the messy writing, late at night (early in the morning). |
That's an already open proposal, you can find it by looking for "unbound", "partially bound", "partially open" generics |
Is it possible for generic attributes to work on .NET Framework? In the past, some "unsupported" features have become compatible just by copying a few types (e.g. |
Nope. The generic attributes feature requires a runtime change and the change won't be backported to .NET Framework. |
@hez2010 I had a feeling that would be the case. Thanks for confirming. 🙂 |
@seanblue It should be possible, however, to write something that directly reads and parses metadata from the assembly file, without using the reflection infrastructure. Probably something based off of Mono.Cecil would do the job, depending on what you want. |
In the past, to get around this, I will have a generic attribute base class and then create a non-generic attribute that inherits from it and use that. I also use a similar approach when I want to provide values to an attribute that are classes/disallowed types. ie:
|
@BlinD-HuNTeR I don't have an immediate use case for generic attributes anyway. I was just updating to C# 11 and trying to see what worked. @TonyValenti I came to the same conclusion. As long as you only use a non-generic subclass of the generic attribute, everything seems to work fine. Not ideal, but still better than nothing. |
See
The text was updated successfully, but these errors were encountered: