dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.97k stars 4.65k forks source link

Help required on using DynamicallyAccessedMembersAttribute in .NET Standard 2.0 Libraries #56612

Open TheCakeMonster opened 3 years ago

TheCakeMonster commented 3 years ago

An issue was raised to have DynamicallyAccessedMembersAttribute exposed for use by libraries targeting downlevel runtimes, allowing .NET Standard libraries to have the appropriate behaviours applied by the linker.

https://github.com/dotnet/runtime/issues/36656

The decision was that this need not be done. One commenter stated that .NET 5 is the new .NET Standard, but that isn't true in the wider world - many people have systems that span .NET Framework and .NET Core, and .NET 5 (and even .NET Standard 2.1) ignores the existence of .NET Framework and the legacy world from which our systems are slowly migrating - slowly being the operative word.

Development teams need their code to be compatible with .NET Framework for some time to come, and the fewer hoops there are to jump through to achieve it, the better. Code in shared libraries that are used on both .NET Framework and .NET Core should continue to compile for .NET Standard 2.0 for an extended period, as we need time to move away from .NET Framework, whilst continuing to add newer features to that code that only impact the behaviour when running on .NET Core.

The post says that there is a way to overcome the problem, but it isn't clear to me exactly what that is. The quotation which suggests this says "compile internal versions of these attributes into the library". Can someone help us understand what that means, and indeed whether it is possible to create a .NET Standard library that does this, for everyone to reference and use?

There are two possible outcomes of this issue that would resolve the request:

  1. A blog post that is easily discoverable, and shows how to overcome this and make .NET Standard 2.0 libraries linker friendly
  2. A .NET Standard library package being published that people can reference to achieve the desired result, if this is technically feasible

The solution probably lies in the way the attribute is recognised. The text seems to suggest that another attribute could be created and still be recognised as being the same attribute, despite residing in a different assembly. Is that true? That doesn't feel right somehow; I would have expected an attribute with the same name in a different assembly to be unrecognised by the linker.

ghost commented 3 years ago

Tagging subscribers to this area: @vitek-karas, @agocke, @vsadov See info in area-owners.md if you want to be subscribed.

Issue Details
An issue was raised to have DynamicallyAccessedMembersAttribute exposed for use by libraries targeting downlevel runtimes, allowing .NET Standard libraries to have the appropriate behaviours applied by the linker. https://github.com/dotnet/runtime/issues/36656 The decision was that this need not be done. One commenter stated that .NET 5 is the new .NET Standard, but that isn't true in the wider world - many people have systems that span .NET Framework and .NET Core, and .NET 5 (and even .NET Standard 2.1) ignores the existence of .NET Framework and the legacy world from which our systems are slowly migrating - slowly being the operative word. Development teams need their code to be compatible with .NET Framework for some time to come, and the fewer hoops there are to jump through to achieve it, the better. Code in shared libraries that are used on both .NET Framework and .NET Core should continue to compile for .NET Standard 2.0 for an extended period, as we need time to move away from .NET Framework, whilst continuing to add newer features to that code that only impact the behaviour when running on .NET Core. The post says that there is a way to overcome the problem, but it isn't clear to me exactly what that is. The quotation which suggests this says "compile internal versions of these attributes into the library". Can someone help us understand what that means, and indeed whether it is possible to create a .NET Standard library that does this, for everyone to reference and use? There are two possible outcomes of this issue that would resolve the request: 1. A blog post that is easily discoverable, and shows how to overcome this and make .NET Standard 2.0 libraries linker friendly 2. A .NET Standard library package being published that people can reference to achieve the desired result, if this is technically feasible The solution probably lies in the way the attribute is recognised. The text seems to suggest that another attribute could be created and still be recognised as being the same attribute, despite residing in a different assembly. Is that true? That doesn't feel right somehow; I would have expected an attribute with the same name in a different assembly to be unrecognised by the linker.
Author: TheCakeMonster
Assignees: -
Labels: `area-AssemblyLoader-coreclr`, `untriaged`
Milestone: -
vitek-karas commented 3 years ago

The tools recognize the attributes only by the namespace + type name - no matter which assembly it came from. All you need to do is define the attribute in your assembly. We do this for our NuGet packages which target netstandard as well: https://github.com/dotnet/runtime/blob/3bd0acf30587c88a1448a51a852871e3407aff5f/src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj#L21-L25

We do it by compiling the source file for that attribute into the library - you can copy the source file from the github repo and do the same.

/cc @eerhardt

You're definitely right that this needs some kind of doc or guidance.

ghost commented 3 years ago

Tagging subscribers to 'linkable-framework': @eerhardt, @vitek-karas, @LakshanF, @sbomer, @joperezr See info in area-owners.md if you want to be subscribed.

Issue Details
An issue was raised to have DynamicallyAccessedMembersAttribute exposed for use by libraries targeting downlevel runtimes, allowing .NET Standard libraries to have the appropriate behaviours applied by the linker. https://github.com/dotnet/runtime/issues/36656 The decision was that this need not be done. One commenter stated that .NET 5 is the new .NET Standard, but that isn't true in the wider world - many people have systems that span .NET Framework and .NET Core, and .NET 5 (and even .NET Standard 2.1) ignores the existence of .NET Framework and the legacy world from which our systems are slowly migrating - slowly being the operative word. Development teams need their code to be compatible with .NET Framework for some time to come, and the fewer hoops there are to jump through to achieve it, the better. Code in shared libraries that are used on both .NET Framework and .NET Core should continue to compile for .NET Standard 2.0 for an extended period, as we need time to move away from .NET Framework, whilst continuing to add newer features to that code that only impact the behaviour when running on .NET Core. The post says that there is a way to overcome the problem, but it isn't clear to me exactly what that is. The quotation which suggests this says "compile internal versions of these attributes into the library". Can someone help us understand what that means, and indeed whether it is possible to create a .NET Standard library that does this, for everyone to reference and use? There are two possible outcomes of this issue that would resolve the request: 1. A blog post that is easily discoverable, and shows how to overcome this and make .NET Standard 2.0 libraries linker friendly 2. A .NET Standard library package being published that people can reference to achieve the desired result, if this is technically feasible The solution probably lies in the way the attribute is recognised. The text seems to suggest that another attribute could be created and still be recognised as being the same attribute, despite residing in a different assembly. Is that true? That doesn't feel right somehow; I would have expected an attribute with the same name in a different assembly to be unrecognised by the linker.
Author: TheCakeMonster
Assignees: -
Labels: `area-AssemblyLoader-coreclr`, `linkable-framework`, `untriaged`
Milestone: -
TheCakeMonster commented 3 years ago

@vitek-karas Thank you, that's very useful information.

I'm having some difficulty on a theoretical level understanding how this would work at runtime. Is there a danger of a type clash when the .NET Standard library executes within an application that targets .NET 5.0?

If there is no danger of a type clash - a duplicate type being detected when the type executes - then is there any reason why the attributes could not be put into .NET Standard 2.0 library and distributed as a Nuget package to be shared by multiple .NET Standard libraries?

eerhardt commented 3 years ago

Is there a danger of a type clash when the .NET Standard library executes within an application that targets .NET 5.0?

No, at runtime they are 2 different types - even though they have the same name. Since they came from 2 different assemblies, they are different types.

then is there any reason why the attributes could not be put into .NET Standard 2.0 library and distributed as a Nuget package to be shared by multiple .NET Standard libraries?

See

TheCakeMonster commented 3 years ago

Thanks for the help everyone. I have it working as described - well, the behaviour is consistent between .NET Standard and .NET 5 libraries anyway. It isn't quire doing what I expected, but that's a topic for another issue.

I've left this open solely as there is some opportunity for documentation of this feature in relation to .NET Standard.

weifenluo commented 1 year ago

There should be packed into a source only package similar to Nullable, which allows you to use .NET's nullable attributes in older target frameworks like .NET Standard 2.0 or the "old" .NET Framework.

eerhardt commented 1 year ago

@weifenluo - https://github.com/Sergio0694/PolySharp has the trimming attributes (and a lot more).

weifenluo commented 1 year ago

@weifenluo - https://github.com/Sergio0694/PolySharp has the trimming attributes (and a lot more).

Cool! using source generator is a brilliant idea compares to source only package - only needed code will be generated.

charlesroddie commented 11 months ago

I've pieced together some instructions on how to get these attributes in netstandard2.0 with the help of this thread and @Sergio0694 .

This is not very hard if these instructions are known. These do need to be added to the various pages. Better would be to have a nuget package to access the attributes.

The current state without support and guidance, as decided in https://github.com/dotnet/runtime/issues/36656#issuecomment-662529466, does not make any sense.

It's not valid to say "net5.0 is the new netstandard". Many libraries are only now moving to netstandard2.0. Anything higher than netstandard2.0 is not appropriate now for most libraries and would generate a lot of complaints if they did that. This is partly due to consumers taking time to update platfoms, but also the fact that not all platforms support higher than netstandard2.0: the only Windows platform supporting AOT and dotnet5+ is Avalonia and you can't expect all software to immediately switch to that. You also can't expect libraries to multitarget to get this feature as that introduces significant extra complexity.

Trimming is now a standard expectation of libraries, but trimming support is made unnecessarily difficult by the lack of netstandard documentation/nuget package for annotations.

vitek-karas commented 11 months ago

Our recommendation is to multi-target the library - so netstandard2.0 and net6.0 currently. With that you can choose to use 3 different options how to introduce the attributes to your code:

The multi-targeting has other benefits, it makes lot of libraries much smaller on net6.0 targets. netstandard2.0 libraries frequently have lot of dependencies on additional packages which are already part of net6.0. So apps using the library even on net6.0 will keep those dependencies in their dependency graph.

Also - you don't really need the test app, it's a nice-to-have for lot of libraries. You can just add IsTrimmable=true to your project and it will turn on analyzers which will report the trim related warnings as well (but this assumed multi-targeting to net6.0, the analyzers won't work on netstandard2.0 targets). The test app is useful to validate that your library dependencies are trim compatible (at least the part of them you're using).

charlesroddie commented 11 months ago

Adding a net6.0 multitarget without actually releasing it would be a good balance, in combination with polysharp, keeping simplicity of the release and development process while getting trimming annotations. It removes the need for a test library.

Libraries currently moving from earlier frameworks to netstandard2.0-only will see large benefits of removing multitargeting, removing ifdefed code everywhere and only having one compile. Adding ifdefs to a solution means that you cannot see while you are developing whether your code typechecks (beyond the current target) - the greyed out code is not checked. I don't see how your library size argument applies since all the extras will get trimmed/linked out.

vitek-karas commented 11 months ago

Multitargeting a library doesn't mean one has to add ifdefs to the code (the trim analysis attributes can be done without ifdefs, it's just one of the options which we've seen people to prefer in some cases). I think I don't understand the second part of your comment...

Library size argument is not for trimmed apps. The reality is that vast majority of apps are not using trimming and are not going to do that anytime soon. If those apps consume netstandard2.0 libraries they will get all of those dependencies. I've seen people "complain" about this already.

Also of note, this is not a trim-only problem. The nullable reference types feature has a similar problem - the netstandard2.0 build of your library will see the framework assemblies without any nullable annotations, which typically means the compiler is going to give up on analyzing those parts. The best way to "fix" that is to build the code targeting net6.0 or newer where the compiler will see fully annotated framework assemblies. Trimming is exactly the same problem, we just chose to disable the analyzer if the framework assemblies are not annotated as its value is very low in that case (and it could be "lying" in some places).