Skip to content

[API Proposal]: Extend System.Environment.SpecialFolder to include newer common folders #70484

@RayKoopa

Description

@RayKoopa

Background and motivation

System.Environment.GetFolderPath is used to query paths to known folders like the user's file folders ("Documents", "Pictures", etc.). The paths to these folders can be redirected in system configuration, which is queried by this method. To use it, a value of the System.Environment.SpecialFolder enum is passed in to query the accompanying folder:

using System;

string videosPath = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);

However, this enum only provides folders that were available in Windows XP days, and does not contain entries for nowadays commonly available personal folders, leaving a "hole" in support for those introduced in Windows Vista or other platforms like Linux and macOS (for example, the user "Downloads" folder).

It is currently required to manually p/invoke the WinAPI or rewrite the existing Linux / macOS folder retrieval logic to ensure getting correct paths. Adding support for these folders via extending the enum will provide a natural and expected way to retrieve these paths from the system configuration without having to go through all that.

Judging from forum discussions, it will also prevent users from "solving" this issue incorrectly by hardcoding and concatenating parts of the folder paths (which would not reflect any redirected paths stored in system configuration) or incorrectly p/invoking system methods themselves:

There have also been several issues requesting the missing folders in the past, but most staled or haven't been tackled since long:

API Proposal

I propose extending the System.Environment.SpecialFolder enum with these values:

SpecialFolder Windows macOS iOS Linux (with XDG) Linux
MyDownloads FOLDERID_Downloads ~/Downloads NSDocumentDirectory/Downloads XDG_DOWNLOAD_DIR ~/Downloads
MySavedGames FOLDERID_SavedGames - - - -
Public FOLDERID_Public ~/Public - XDG_PUBLICSHARE_DIR ~/Public
PublicDesktop FOLDERID_PublicDesktop - - - -
PublicDocuments FOLDERID_PublicDocuments - - - -
PublicDownloads FOLDERID_PublicDownloads - - - -
PublicMusic FOLDERID_PublicMusic - - - -
PublicPictures FOLDERID_PublicPictures - - - -
PublicVideos FOLDERID_PublicVideos - - - -
  • The underlying values are added starting with 0x10000 due to the undocumented fact that the existing values map to Windows' CSIDLs, and 0x10000 is the first invalid CSIDL combination that would not clash.

    • The CSIDL API is deprecated, not used by .NET anymore, and does not allow querying the folders above anyway.
  • ~ is the home directory as it is already determined.

  • XDG... variables are used on Linux if they exist, otherwise the non-XDG path is used as a fallback.

  • The public (sub) folder is per-system on Windows, and per-user on Linux and macOS. iOS has no such folders.

    • Adding Public... sub folders is required for Windows as they may be redirected outside of the parent PublicDirectory. Otherwise it could lead to developers looking for them to incorrectly hardcode parts of their path again by assuming they are children (the same issue currently with the Downloads and Public folders themselves).
  • User folders are prefixed with My as it is currently the case for other user folders like MyPictures or MyDocuments, so that related folders group together more clearly in the enum.

    • MySavedGames is included as it is the only remaining Windows user folder meaningful to developers. While there are other user folders available on Windows, I do not recommend adding as they have a very special meaning only to one Windows app / component:

      SpecialFolder Windows only Used by / meant for
      ❌ MyContacts FOLDERID_Contacts Windows Contacts contact files.
      ❌ MyLinks FOLDERID_Links File Explorer pinned location shortcut files (< Windows 10 only).
      ❌ MySearches FOLDERID_Searches Parameterized Windows Search query files.
  • Folders that do not exist on specific platforms will return String.Empty as they already do for others.

  • VB.NET's SpecialDirectories class will not be extended as it is understood to be a utility class for VB6-style backwards compatibility.

API Usage

The usage naturally extends the options made available through the existing enum:

using System;

string downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDownloads);
string publicPath = Environment.GetFolderPath(Environment.SpecialFolder.Public);
string savedGamesPath = Environment.GetFolderPath(Environment.SpecialFolder.MySavedGames);

Alternative Designs

  • A completely new API available additionally to the existing System.Environment.GetFolderPath (and possibly deprecating it) to make extending special folders more flexibly in the future was discussed at Add a more flexible special folder API #19047, but abandoned.
  • Alternatively, a new enum KnownFolder could be introduced if the existing SpecialFolder should not be touched in any case (s. possible "Risks" below), and an overload could be provided for System.Environment.GetFolderPath accepting those.

Risks

I rate the risks as Low:

  • Code that enumerated over all values of the SpecialFolder enum and depended on the undocumented fact that each underlying value is a Windows CSIDL value now has to check whether the value is < 0x10000 in case it wants to pass only valid values to the deprecated CSIDL Windows API.
  • Code that relied on the undocumented fact that the enum values could be stored in just 2 bytes now has to use a larger data type or ignore the new values with the < 0x10000 check.

The additions here should be merged after fixes to existing paths are done in #68610 to not clash with the changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Runtimeneeds-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions