Skip to content

Improve duplicate #:include handling for file-based C# apps by deduplicating compile inputs #54080

@code-gal

Description

@code-gal

Is your feature request related to a problem? Please describe.

I'm trying to use #:include to compose modular file-based C# apps, but duplicate inclusion through transitive include graphs currently produces a poor experience.

A simple example is:

  • A includes B
  • B includes C
  • A also includes C

In this scenario, the app can still build and run successfully, but the build emits CS2002 because the same source file is passed to the compiler multiple times.

From a file-based app user's perspective, this is not really the same as accidentally invoking the compiler with the same source file twice. It is a natural outcome of source composition through #:include.

Requiring users to manually pre-deduplicate include graphs makes file-based apps less elegant and pushes them toward heavier project-style preprocessing, which works against the lightweight promise of the feature.

Related discussions / implementations:

Describe the solution you'd like

I'd like the SDK to recognize file-based app include graphs and deduplicate repeated source files before passing compile inputs to the compiler.

Preferred behavior:

  • detect duplicate source files arising from #:include expansion
  • normalize / resolve paths first
  • compile each source file only once
  • preserve stable ordering for the first occurrence
  • keep build/run/convert behavior consistent

If possible, I think this should be handled at the SDK / virtual project layer rather than being left to the compiler to surface as a raw CS2002 experience.

If deduplication cannot be implemented immediately, an intermediate improvement would be to provide a more file-based-app-specific, lower-noise diagnostic instead of exposing the traditional duplicate-source warning experience directly.

Alternative solutions considered:

  • leaving the current behavior as-is and expecting users to manually deduplicate include closures
    • works, but does not feel appropriate for a lightweight file-based app model
  • lowering the severity or contextualizing the diagnostic for file-based app scenarios
    • better than today, but still less ideal than SDK-side deduplication
  • runtime conditional inclusion
    • not recommended; build-time declarative conditions would be a better direction for advanced composition than runtime compilation behavior

Additional context

This seems especially important if #:include is expected to scale beyond very small examples.

Without SDK-side deduplication, users are incentivized to add more manual preprocessing and scenario-specific closure management just to avoid duplicate include requests. That may be acceptable in specialized tooling, but it does not seem like the right default burden for typical file-based app users.

The reason I believe dotnet/sdk is the right place for this is that #:include itself appears to be implemented as an SDK / virtual-project feature (dotnet/sdk#52347), while Roslyn support such as editor completion is handled separately (dotnet/roslyn#82625).

If helpful, I can provide a minimal repro based on a file-based app where transitive #:include causes CS2002 even though the app still builds and runs successfully.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-run-fileItems related to the "dotnet run <file>" effortuntriagedRequest triage from a team member
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions