0

I have a need where I would like to have a base class only available to one specific subclass. This is due to a limitation where you cannot define an Attribute as an inner class of a generic.

The reason we want to define the attribute within the generic is when you do that and set its type to protected, that attribute is now only available to subclasses of your base class, keeping your API clean.

As an example, consider the abstract class ExampleMarkupExtension<T> that inherits from MarkupExtension. I want to define the attribute StaticInfoAttribute as an inner class, so it is only available to subclasses of ExampleMarkupExtension<T>.

However, as mentioned, you can't define an attribute as an inner class of a generic, so my workaround is to create a second, non-generic ExampleMarkupExtensionBase class which inherits from MarkupExtension, define StaticInfoAttribute as an inner class there, then have the generic inherit from that class instead.

The issue is now I have an extra class in my hierarchy that anyone can subclass. While it's technically harmless, I like keeping my API surface area clean.

Example

ExampleMarkupExtensionBase : Non-generic abstract base class

This is the class that defines the attribute which should only be available to subclasses of this class. This class only exists because I can't put StaticInfoAttribute within the generic abstract base class ExampleMarkupExtension<T> below.

public abstract class ExampleMarkupExtensionBase : MarkupExtension {

    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=false)]
    protected class StaticInfoAttribute : Attribute {
        public StaticInfoAttribute(string value) => Value = value;
        public readonly string Value;
    }
}

ExampleMarkupExtension<T> : Generic abstract base class

This is the class that holds the static members which are unique to the subclasses of this class. It does this by using the subclass as the type parameter for the base class (see the where clause). The value of the static field is set from the attribute, hence it only being of any use for this specific subclass of ExampleMarkupExtension above.

public abstract class ExampleMarkupExtension<T> : ExampleMarkupExtensionBase
where T : ExampleMarkupExtension<T> {

    static ExampleMarkupExtension() => Value = typeof(T).GetRequiredCustomAttribute<StaticInfoAttribute>().Value;

    public static readonly string Value;

    public sealed override object ProvideValue(IServiceProvider serviceProvider)
        => Value;
}

Rationale

Some may ask what's the harm of leaving it as-is. Technically, there's nothing wrong with letting a person subclass ExampleMarkupExtensionBase. There's just no point to it.

For that matter, there's technically no harm with moving the StaticInfoAttribute outside of the class as well, removing the need for the non-generic base class entirely. I just chose the former approach as it's more important to restrict the attribute's usage than it is to stop someone from subclassing the base class.

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • in perfect world developers are using ResourceDictionaries, and {StaticResource} / {DynamicResource} extensions – ASh Nov 08 '20 at 14:41
  • @ASh, try not to focus on the fact I'm using a `string` here. That is for illustrative purposes only. The actual attribute and the generic abstract class perform a lot more logic than a simple resource lookup. I've since updated the question and examples to be more clear so people don't make the mistake of thinking I'm trying to define strings this way because I would agree if that was the case, it would be crazy to do it this way. (And on that note, if you voted this down for that reason, can I appeal to you to reverse doing so?) – Mark A. Donohoe Nov 09 '20 at 00:52
  • 1
    I think this is fairly well known. See https://stackoverflow.com/questions/294216/why-does-c-sharp-forbid-generic-attribute-types. As a side note you can use [EditorBrowsable(EditorBrowsableState.Never)] to hide the class from projects that reference it, however not from within the same project. – Jerry Nov 09 '20 at 01:41
  • That's close. `InitInfoAttribute` isn't itself a generic. The containing class `ExampleMarkupExtension` that is. Then again, by making the attribute an inner-class, I guess you are making it a generic indirectly so perhaps they are the same thing. Still, I like your idea of the browsable attribute. – Mark A. Donohoe Nov 09 '20 at 01:44
  • 1
    You are absolutely making it generic as it expands out to `ExampleMarkupExtension.InitInfoAttribute` – Jerry Nov 09 '20 at 01:45

1 Answers1

0

As per our comments, you can use the EditorBrowsable attribute to hide the method from projects that reference it. Note however it just hides it from IntelliSense and if a user was to type out ExampleMarkupExtensionBase that the class still exists and is valid to be used.

[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class ExampleMarkupExtensionBase : MarkupExtension

enter image description here

Jerry
  • 1,477
  • 7
  • 14
  • The attribute doesn't work if you reference the project, only if you reference the .dll – Jerry Nov 09 '20 at 02:09
  • Yeah, that much I know. That's because it's more for consumers of your DLLs, not you as the designer so being a project reference basically ignores that attribute. :) – Mark A. Donohoe Nov 09 '20 at 02:10