C# tutorials > Modern C# Features > C# 6.0 and Later > What are file-local types?

What are file-local types?

File-local types in C# (introduced in C# 11) provide a way to restrict the visibility of a type (class, struct, interface, enum, or delegate) to the file in which it's declared. This is done using the file modifier.

They are a powerful tool for encapsulation and can help reduce naming conflicts, especially in large projects or when generating code. Think of them as a more granular version of internal, limiting scope to a single file rather than an entire assembly.

Basic Syntax

The file keyword is placed before the type declaration (class, struct, interface, enum, or delegate). This indicates that MyFileLocalClass is only accessible within the file it is defined in. Outside of this file, it's as if the class doesn't exist.

file class MyFileLocalClass
{
    public void DoSomething()
    {
        Console.WriteLine("Doing something in MyFileLocalClass.");
    }
}

Example Usage

In the MyFile.cs example, MyFileLocalClass is declared with the file modifier. Inside the same file, MyOtherClass can instantiate and use MyFileLocalClass without any issues. However, in AnotherFile.cs, attempting to create an instance of MyFileLocalClass results in a compile-time error because it's outside the defined scope of the type.

// File: MyFile.cs
file class MyFileLocalClass
{
    public void DoSomething()
    {
        Console.WriteLine("Doing something in MyFileLocalClass.");
    }
}

public class MyOtherClass
{
    public void UseFileLocal()
    {
        var instance = new MyFileLocalClass(); // This is OK because it's in the same file
        instance.DoSomething();
    }
}

// File: AnotherFile.cs
// The following code will cause a compile-time error
// because MyFileLocalClass is not accessible in this file.
//MyFileLocalClass anotherInstance = new MyFileLocalClass(); // Error: MyFileLocalClass is inaccessible due to its protection level

Concepts Behind the Snippet

The core concept here is encapsulation. File-local types allow you to hide implementation details within a single file. This reduces the chances of naming conflicts and makes the code more maintainable by preventing accidental dependencies on internal types from other parts of your project. They improve code modularity by promoting isolated units of code within a file.

Real-Life Use Case Section

Consider a code generation scenario where you generate helper classes or structs specific to a particular process within a single file. Using file-local types prevents these generated types from interfering with other parts of the codebase. Another use case is in implementing complex algorithms or data structures where you want to define internal helper types that are only relevant within that specific implementation.

Imagine a parser that needs internal structures for processing; making them file-local prevents these structures from polluting the global namespace.

Best Practices

  • Use file-local types to encapsulate implementation details within a single file.
  • Avoid making file-local types public members of other types (e.g., a public property returning a file-local type) because this will expose an inaccessible type outside the defining file and result in compile-time errors.
  • When deciding between internal and file accessibility, choose file if the type is only needed within a single file.

Interview Tip

Be prepared to explain the difference between file, internal, protected, public, and private access modifiers. Also, be ready to discuss the benefits of encapsulation and how file-local types contribute to it. A good understanding of these concepts demonstrates your ability to write clean, maintainable, and robust code.

When to Use Them

Use file-local types when you need to create a type that should only be accessible within a single file. This is particularly useful for helper classes, structs, or enums that are specific to a particular implementation within that file. They are not useful when you intend to reuse that specific type across different classes or files, in that case, you should use internal, or even expose the type publicly if it is part of the intended public API.

Memory Footprint

File-local types do not inherently have a different memory footprint compared to other types. The memory usage is determined by the data members of the type, not its accessibility modifier. The file modifier primarily impacts compile-time visibility, not runtime behavior or memory allocation.

Alternatives

Before C# 11, you would typically use private or internal accessibility to achieve a similar effect. However, private limits visibility to the enclosing type, while internal exposes the type to the entire assembly. File-local types offer a more precise level of control when you want to restrict visibility to a single file. Another alternative is to use nested classes within the class needing the helper, making them private to the enclosing type but they will belong logically to that same class and not be reusable in other classes of the file.

Pros

  • Improved Encapsulation: Limits the scope of types, reducing naming conflicts and accidental dependencies.
  • Increased Code Clarity: Makes it easier to understand the intended use of a type by limiting its visibility.
  • Reduced Complexity: Simplifies code maintenance by preventing external code from relying on internal implementation details.

Cons

  • Limited Reusability: File-local types cannot be reused in other files, which may require duplicating code if the same type is needed in multiple files.
  • Potential for Confusion: Developers unfamiliar with the file modifier may be confused by the limited accessibility of these types.
  • Overuse: It's possible to overuse file-local types, leading to unnecessary code duplication and a less cohesive codebase. It's a bad practice to create multiple types file-local that duplicate code across multiple files instead of finding a way to reuse one type.

FAQ

  • Can I use file-local types in interfaces?

    Yes, you can declare interfaces as file-local using the file modifier.
  • Can I use file-local types as return types or parameters in public methods?

    No, you cannot expose file-local types in public APIs because that would violate the principle of encapsulation and lead to compile-time errors when the API is consumed from outside the file.
  • What happens if I have two files with the same file-local type name?

    Each file will have its own distinct type with that name. They are treated as completely separate types by the compiler. There will be no naming conflict because their scope is limited to their respective files.
  • Is the `file` modifier valid for other members besides types?

    No, the `file` modifier is exclusively for types (classes, structs, interfaces, enums, and delegates).