runtime: Add SingleFileUnsupportedAttribute

Background and Motivation

There are different APIs that are unusable in single-file applications (see https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file). This attribute’s intent to help us mark methods that are single-file unfriendly so that tools can better reason about them (see https://github.com/dotnet/runtime/issues/44488.)

As a first step after adding this attribute to the runtime (if approved) we intend to annotate the following APIs inside the runtime libraries: Assembly.Location, Module.FullyQualifiedName, Module.Name, Assembly.GetFile, Assembly.GetFiles, Assembly.CodeBase, Assembly.EscapedCodeBase, AssemblyName.CodeBase, AssemblyName.EscapedCodeBase.

Proposed API

namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false, AllowMultiple = false)]
    public sealed class SingleFileUnsupportedAttribute : Attribute
    {
        public SingleFileUnsupportedAttribute(string message)
        {
            Message = message;
        }

        public string Message { get; }

        public string Url { get; set; }
    }
}

Usage Examples

This attribute should be used to annotate single-file unfriendly methods:

namespace System.Reflection
{
    public abstract partial class Assembly : ICustomAttributeProvider, ISerializable
    {
        ...
-        public virtual string Location => throw NotImplemented.ByDesign;
+        public virtual string Location
+        {
+            [SingleFileUnsupported("Always returns an empty string for single-file apps",
+                "<url pointing to a docs page with more information and different options on how to deal with this>")] 
+           get { throw NotImplemented.ByDesign; }
+        }

        ...
+	[SingleFileUnsupported("Throws IOException for single-file apps", "<url>"]
        public virtual FileStream? GetFile(string name) { throw NotImplemented.ByDesign; }

        ...	
    }
}

Risks

None. With this attribute we expect to better inform users whenever they make use of incompatible methods in their single-file apps.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (20 by maintainers)

Most upvoted comments

After some discussion with @agocke and @vitek-karas we agreed to this API shape instead:

namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Constructor |
                    AttributeTargets.Event |
                    AttributeTargets.Method |
                    AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public sealed class RequiresAssemblyFilesAttribute : Attribute
    {
        public RequiresAssemblyFilesAttribute();
        public string Message { get; set; }
        public string Url { get; set; }
    }
}

@dotnet/fxdc, please yell if you see any concerns.

Why RequiresAssemblyFilesAttribute and not RequiresAssemblyFileAttribute ?

This was intentional. A relatively common confusion is that the attribute talks about the entire application - not about any single assembly. So we wanted to make it a bit clearer that it’s a statement about “multiple assemblies” (as in assemblies in the application).

We should take a diagnostic id

This sounds like it’s based off of our decision making for Obsolete, but after thinking about it I’m not sure it’s applicable to this scenario. @terrajobst let’s set up a quick meeting to discuss trade-offs here.

We shouldn’t take a message because they are hard to change

I don’t agree with this. The primary reason to have the message is so that the annotated code can provide a domain specific feedback. For example DI frameworks:

  • Without the message I would get something like Calling PopulateDIFromApp is not compatible with single-file, it may not work as expected (or similar vague message). This is basically unactionable - user will have to search for this and hopefully will find some SO answer explaining what this means and how to fix it. Yes, the URL could “solve” this, but it’s not that nice.
  • With the message, the DI framework could make it look like Calling PopulateDIFromApp will not work in single-file, instead, please register all necessary types via RegisterDIProvider - this is actually actionable to the end-user.

We’re trying to model this attribute to be similar to the RequiresUnreferencedCodeAttribute - which does have a message (exactly for the reason mentioned above - if I recall the API review discussion correctly).

Unsupported seems to imply it throws;

I agree that calling it “Unsupported” doesn’t feel right. I like the RequiresSomething templated much better. This goes back to the discussion we had around RequiresUnreferencedCode - the reasoning was: The attribute should describe the required capability/behavior which the code needs to operate correctly (or which it was designed for). It should not be tied any specific technical case/tool.

This kind of goes into the discussion of: Do we want to treat Xamarin.Android apps as single-file apps as well? And what about most native-AOT apps (iOS, CoreRT), those are technically also single-file… Even Blazor is single-file-like to some extent. This would actually question the “single-file” part on its own (is Android app single-file - nobody will know, since it’s not used as a term in that context).

Let’s say that the goal is to provide a way to mark an API as requiring a capability, and having a separate API that can detect that capability, but at a higher level of abstraction.

I think we could try to go beyond that. It’d be great to use the same approach to annotate APIs which have different behaviour based on other factors. I listed a few earlier but one can see such capabilities to be useful even for configurable options like InvariantGlobalization or other feature switches.

We would like to use attributes for annotation. They’re common, user-understandable, constant at the metadata level, and do not affect the type system.

Using attributes is one thing and interpreting them is a different thing. As we learnt with platform compatibility attribute it’s better to design from begging for intersections and exclusions.

/cc @jeffhandley