Introducing File Scoped Types In C# 11

File Scoped Types in C# 11

Since the evaluation of C#, we have seen many access modifiers introduced, and similarly, in C# 11, file-scoped type is a new access modifier mainly designed for code generator authors where they can create a type that is scoped to that file and not visible to outside types and with this, we can avoid name collisions with other type names in the same namespace and assembly. 

Here code generator is a piece of code that executes and generates a code at compilation time and includes this generated code with actual code and executes at runtime.  As an author of the code generator, we cannot predict the existing type names in the user project and they name the top-level types based on the logic implemented. 

The users who want to use these code generators to reduce their effort and get readymade code, need to identify and change the type names accordingly if there is any of their project type names collide with the names generated by the code generators. We can overcome such challenges with the introduction of this file-scoped type. By using this file type, the scope of the type will be at a file level, which means, it is accessible and visible only to the implementation within that file. So, we can use the same type names outside of the file or other files within the same namespace and assembly and we never see the duplicate type name compilation errors. This way, whatever code generates by the code generator can directly use without any changes to the existing type names.

Let's understand more of this type and its advantages with some simple code blocks below.

//File1.cs 

namespace sampleNS 
{ 
    file class Maths 
    { 
        public int Add(int x, int y) => (x + y); 
        public int Multiply(int x, int y) => (x * y); 
    } 

    class Calculations 
    { 
        public int SomeCalculation(int x, int y) 
        { 
            Maths maths = new(); 
            return maths.Add(maths.Add(x, y), maths.Multiply(x, y));   
        } 
    } 
}
//File2.cs 

namespace sampleNS 
{ 
    file class Maths 
    { 
        public int Add(int x, int y) => (x + y + 2); 
        public int Multiply(int x, int y) => (x * y * 2); 
    } 

    class Calculations 
    { 
        public int SomeCalculation(int x, int y) 
        { 
            Maths maths = new(); 
            return maths.Add(maths.Add(x, y), maths.Multiply(x, y));   
        } 
    } 
}
//program.cs 

internal class Maths 
{ 
    public int Add(int x, int y) => (x + y); 
    public int Multiply(int x, int y) => (x * y); 
}

Above, we are seeing 3 code blocks in 3 different files. Where we observed all the files having the same class type name Maths. But, every file has its own implementation of this class and it is scoped to all other types of that file. The files File1.cs and File2.cs have a Maths class declared with a file access modifier in the program.cs file, it is declared as default internal. Here the only difference is the class which is in the program.cs will be accessible to all the files in the assembly and the types which are decorated with files are scoped only to that file and outside cannot be accessible.

Acceptable Type 

Following are the types that are accepted to add file modifiers.

  • Class
  • Interface
  • Struct
  • Enum
  • Delegate

When a type has the file modifier, it is said to be a file-local type.

Limitations

File-local type cannot be accepted with other accessibility modifiers like public, and internal. The file type is independent. 

public file class Maths {} // error 
file class Maths {} // OK

File-scope type will not be accessible if it is in the same file but with a different namespace as below but accepted if they provide a fully qualified name of the type. 

//File2.cs 

namespace sampleNS 
{ 
    file class Maths 
    { 
        public int Add(int x, int y) => (x + y + 2); 
        public int Multiply(int x, int y) => (x * y * 2); 
    } 
} 

namespace sampleNS.Test 
{  
    class Calculations 
    { 
        public int SomeCalculation(int x, int y) 
        { 
            Maths maths = new(); // error 
            sampleNS.Maths math = new() ; // OK 
            return maths.Add(maths.Add(x, y), maths.Multiply(x, y));   
        } 
    } 
}

In the case of the file type to an interface, the implementing classes should be scoped to file.

file Interface Iinterface 
{ 
     void Method1(); 
} 

Class: Iinterface 
{ 
    public void Method1() { } //error 
}

Happy Coding :)