Why does C# forbid generic attribute types?

C#Generics.Net Attributes

C# Problem Overview


This causes a compile-time exception:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

I realize C# does not support generic attributes. However, after much Googling, I can't seem to find the reason.

Does anyone know why generic types cannot derive from Attribute? Any theories?

C# Solutions


Solution 1 - C#

Well, I can't answer why it's not available, but I can confirm that it's not a CLI issue. The CLI spec doesn't mention it (as far as I can see) and if you use IL directly you can create a generic attribute. The part of the C# 3 spec that bans it - section 10.1.4 "Class base specification" doesn't give any justification.

The annotated ECMA C# 2 spec doesn't give any helpful information either, although it does provide an example of what's not allowed.

My copy of the annotated C# 3 spec should arrive tomorrow... I'll see if that gives any more information. Anyway, it's definitely a language decision rather than a runtime one.

EDIT: Answer from Eric Lippert (paraphrased): no particular reason, except to avoid complexity in both the language and compiler for a use case which doesn't add much value.

Solution 2 - C#

An attribute decorates a class at compile-time, but a generic class does not receive its final type information until runtime. Since the attribute can affect compilation, it has to be "complete" at compile time.

See this MSDN article for more information.

Solution 3 - C#

I don't know why it's not allowed, but this is one possible workaround

[AttributeUsage(AttributeTargets.Class)]
public class ClassDescriptionAttribute : Attribute
{
    public ClassDescriptionAttribute(Type KeyDataType)
    {
        _KeyDataType = KeyDataType;
    }

    public Type KeyDataType
    {
        get { return _KeyDataType; }
    }
    private Type _KeyDataType;
}


[ClassDescriptionAttribute(typeof(string))]
class Program
{
    ....
}

Solution 4 - C#

This is not truly generic and you still have to write specific attribute class per type, but you may be able to use a generic base interface to code a little defensively, write lesser code than otherwise required, get benefits of polymorphism etc.

//an interface which means it can't have its own implementation. 
//You might need to use extension methods on this interface for that.
public interface ValidatesAttribute<T>
{
    T Value { get; } //or whatever that is
    bool IsValid { get; } //etc
}

public class ValidatesStringAttribute : Attribute, ValidatesAttribute<string>
{
    //...
}
public class ValidatesIntAttribute : Attribute, ValidatesAttribute<int>
{
    //...
}

[ValidatesString]
public static class StringValidation
{

}
[ValidatesInt]
public static class IntValidation
{

}

Solution 5 - C#

This is a very good question. In my experience with attributes, I think the constraint is in place because when reflecting on an attribute it would create a condition in which you would have to check for all possible type permutations: typeof(Validates<string>), typeof(Validates<SomeCustomType>), etc...

In my opinion, if a custom validation is required depending on the type, an attribute may not be the best approach.

Perhaps a validation class that takes in a SomeCustomValidationDelegate or an ISomeCustomValidator as a parameter would be a better approach.

Solution 6 - C#

This is not currently a C# language feature, however there is much discussion on the official C# language repo.

From some meeting notes:

> Even though this would work in principle, there are bugs in most > versions of the runtime so that it wouldn't work correctly (it was > never exercised). > > We need a mechanism to understand which target runtime it works on. We > need that for many things, and are currently looking at that. Until > then, we can't take it. > > Candidate for a major C# version, if we can make a sufficient number > of runtime versions deal with it.

Solution 7 - C#

Generic Attributes has been added to C# 10 as a preview feature, meaning that you must set <LangVersion> to Preview to enable this feature; And the feature may change before its final release.

Note that there are some restrictions to the feature like:

> You can apply a fully closed constructed generic attribute. In other words, all type parameters must be specified. For example, the following is not allowed: > > public class GenericType > { > [GenericAttribute()] // Not allowed! generic attributes must be fully closed types. > public string Method() => default; > }

and

> The type arguments must satisfy the same restrictions as the typeof operator. Types that require metadata annotations aren't allowed. Examples include the following: > > - dynamic > - nint, nuint > - string? (or any nullable reference type) > - (int X, int Y) (or any other tuple types using C# tuple syntax). > > These types aren't directly represented in metadata. They include annotations that describe the type. In all cases, you can use the underlying type instead: > > - object for dynamic. > - IntPtr instead of nint or unint. > - string instead of string?. > - ValueTuple<int, int> instead of (int X, int Y).

Source: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#generic-attributes

Solution 8 - C#

My workaround is something like this:

public class DistinctType1IdValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type1> validator;

    public DistinctIdValidation()
    {
        validator = new DistinctValidator<Type1>(x=>x.Id);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

public class DistinctType2NameValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type2> validator;

    public DistinctType2NameValidation()
    {
        validator = new DistinctValidator<Type2>(x=>x.Name);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

...
[DataMember, DistinctType1IdValidation ]
public Type1[] Items { get; set; }

[DataMember, DistinctType2NameValidation ]
public Type2[] Items { get; set; }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBryan WattsView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#GalacticCowboyView Answer on Stackoverflow
Solution 3 - C#GeekyMonkeyView Answer on Stackoverflow
Solution 4 - C#nawfalView Answer on Stackoverflow
Solution 5 - C#ichibanView Answer on Stackoverflow
Solution 6 - C#Owen PaulingView Answer on Stackoverflow
Solution 7 - C#Hossein EbrahimiView Answer on Stackoverflow
Solution 8 - C#razonView Answer on Stackoverflow