In this article, we will learn about the File Access Modifier in C#. We will focus on what problems it solves and how to use it. 

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

What Is the File Access Modifier

We use an access modifier to define the scope and visibility of a class or its members.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

In C#, we use the file access modifier to restrict the scope and visibility of a top-level type to the file where we declare it. Using this access modifier results in the declaration of types known as file-local types.

Usages and Benefits of the File Access Modifier

Above all, the file modifier helps:

  • hiding implementation details from the rest of the compilation
  • avoiding conflicts with already existing types
  • source generators use a namespace containing user-defined types without name conflicts

Until now, code generators had to scan existing user-defined types to prevent name conflicts, making code generation time-consuming.

To help speed up source generators and to avoid these possible collisions, the file access modifier was added.

The compiler restricts file-local types to the file. When defining types with the same name but in different files, the compiler ensures that it gives them distinct names to avoid naming conflicts. During name resolution, the file-local type takes precedence within its file.

Additionally, developers can use this modifier while constructing libraries to replace private nested classes employed internally and as a substitute for extension method classes confined to a single file.

Let’s look at this access modifier in more detail.

Working With the File Access Modifier

Only top-level types such as classes, structs, enums, delegates, interfaces, records, and record structs can use the file access modifier.

However, we can not use a file-local type:

  • as a return type or parameter for a member that is more visible than the file scope
  • as a field member of a type more visible than the file scope
  • with other accessibility modifiers besides the default – internal
  • with nested types

Let’s create a ContentUploadService.cs file:

namespace FileAccessModifierInCsharp;

public static class Logger
{
    public static string GetMessage(DateTime datetime, int fileId)
    {
        return $"{datetime} - Upload - File ID: {fileId}";
    }
}

public static class ContentUploadService
{
    public static string GetMessage(DateTime datetime, int fileId)
    {
        return Logger.GetMessage(datetime, fileId);
    }
}

Here, we make a public ContentUploadService class and a public static class named Logger.

After that, we create another source file named ContentDownloadService.cs, which also contains a static type called Logger and a public ContentDownloadService class:

namespace FileAccessModifierInCsharp;

public static class Logger
{
    public static string GetMessage(DateTime datetime, int fileId)
    {
        return $"{datetime} - Download - File ID: {fileId}";
    }
}

public static class ContentDownloadService
{
    public static string GetMessage(DateTime datetime, int fileId)
    {
        return Logger.GetMessage(datetime, fileId);
    }
}

Now, we have errors because a public type named Logger is defined in both files, and both of them also have a method with the same name and parameters:

Error CS0101 The namespace 'Content' already contains a definition for 'Logger.'

Now, to solve this error, we need to change the public access modifier to file on at least one of the two Logger classes, thus restricting its visibility to the file where it’s defined:

file static class Logger

After that, the compiler error disappears, and each type can use the Logger definition in its file.

Conclusion

In conclusion, we have learned how to use the file access modifier and what it does. The file-local types are beneficial for code generation as they ease the generation of type names without worrying about name conflicts, which is the main reason for introducing this access modifier to C#.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!