dotnet / project-system

The .NET Project System for Visual Studio
MIT License
968 stars 387 forks source link

For multi-tfm projects that contain a class with the designer attribute constantly set and resets the designer attribute #2001

Open srivatsn opened 7 years ago

srivatsn commented 7 years ago

Reported here - https://developercommunity.visualstudio.com/content/problem/38882/project-that-targets-both-net46-and-netstandard15.html


Version. Visual Studio 2017 RTM. All extensions disabled.

  1. Start with an empty .NET Standard class library project. Edit the project file to so that it targets net46 and netstandard1.5 (e.g. net46;netstandard1.5) - this actually fails with any version of net and netstandard.
  2. Add class that is a subclass of System.ComponentModel.Component. It can be as simple as this:
    #if NET46
    using System.ComponentModel;
    namespace ClassLibrary1
    {
    public class ComponentSubclass : Component
    {
    }
    }
    #endif
  3. Observe that the project file is constantly changing. Visual Studio seems to be constantly adding/removing the following:
    <ItemGroup>
    <Compile Update="ComponentSubclass.cs">
      <SubType>Component</SubType>
    </Compile>
    </ItemGroup>

    As a result, the project and any other projects referencing this project are constantly recompiling.

srivatsn commented 7 years ago

I assume this doesn't happen anymore because roslyn stopped setting the subtype for CPS projects?

davkean commented 7 years ago

Yes, it would have stopped but we'll need a long solution for this.

tmeschter commented 7 years ago

There are a couple of distinct problems in play.

  1. Handling the situation where a file contains a component when targeting one framework, but not another.
  2. Setting the <SubType> metadata is very slow in CPS.

Strictly speaking this issue is only about the first problem.

System.ComponentModel.Component exists in net46 but not netstandard1.5, which is why the preprocessor is being used to only include the file contents when targeting net46. However, the SubType metadata applies to the file as a whole, not to an individual class. The Designer attribute scanner processes this file once for each target framework, and for net46 it determines that the file should have <SubType>Component</SubType> and for netstandard1.5 it should have <SubType>None</SubType> (which is the default so the subtype ends up being removed entirely). So each scan twiddles the metadata, changing the project file for both targets, which kicks off more scans, and more twiddling, and so on.

I see several possible solutions and mitigations:

  1. Make the Designer attribute scanner multi-targeting aware. It would need to combine the results of multiple target-specific scans before making changes. If any target indicates that the file is "designable" then the <SubType /> metadata is set. Only if they all agree that it is not would the metadata be removed. We would need to figure out what to do if different targets want different values for the metadata--one value would need to win consistently.
  2. The user could conditionally include/exclude the entire file in the project file, rather than using the preprocessor.
  3. The Designer attribute scanner could make an up-front check for the System.ComponentModel.Component type. If it doesn't exist in the target framework, don't bother scanning any files in the project. Since we don't scan, we won't attempt to change the <SubType /> one way or the other.

Note these options are not mutually exclusive. At the minimum we should implement 3 anyway as it avoids pointless scans.