NDepend

Improve your .NET code quality with NDepend

C# 11 File Scoped Types

C# 11 File Scoped Type Title

C#11 added the file scoped types feature: a new file modifier that can be applied to any type definition to restrict its usage to the current file. This way we can have several classes with the same name (namespace.name) in a single project. This is shown through the project below that contains two classes named ConsoleApp1.Answer :

C# 11 File Scope Type

Here are a few remarks:

  • The types with the file modifier can be indirectly accessed outside of their source file. In the program above we rely on the internal classes InternalClassFromFile1 and InternalClassFromFile2 to run both Answer classes from Program.
  • Such file class can also be indirectly used outside its source file through an interface this way:

  • Any kind of type can be marked with the file modifier: classinterfacerecordstructenumdelegate.
  • file cannot be used with another modifier like internal or public.
  • file partial can be used as long as all the type definitions belong to the same file:

  • The file modifier doesn’t apply to types nested in a parent type. Also it doesn’t apply to methods properties, events and fields but the language design note explains: “Leaves the design space open for non-type file-scoped members to come along later.”
  • As the screenshot below shows, it is possible to have in a project, a single internal or public class with full name ConsoleApp1.Answer, and one or several file ConsoleApp1.Answer. The only drawback is that the public class cannot be used within source files containing a file scoped class.

C# 11 File Scope Type No Collision With Another Type

Why this feature?

Let’s underline that namespace remains the preferred way to avoid type name collision. However we can imagine several use cases for this feature:

  • Generated code: Typically generated templates often use the same class name again and again, like ItemInfo or DataSet. To prevent collision we had to either nest an Item class in a parent class or maintain an index in name for each generated version, like Item1, Item2 … Thus the keyword file can significantly ease code generation.
  • Extension method: Extension method is nowadays a popular language construct for C# developers. However there can be extension method naming collision. With this file visibility restriction it is now easy to have extension methods restricted to a single file. Another common issue with extension method -solved by this file visibility restriction- is that they pollute intellisense dropdowns.
  • Nested class: To solve the naming collision within a project, a common solution mentioned above is that we used to declare private nested classes. However this is not clean code because an extra indentation level is added, and too much indentation clutters code. Thus the file keyword will help with this concern.
  • Module and Encapsulation: Typically what you consider as a module or a component is not a single class but a few highly cohesive types. For encapsulation purposes a pattern was to nest private implementation details within private nested classes and types. This file scope makes such encapsulation pattern cleaner.
  • Test: Often tests structure is standardized and each test class has its own DataSet class for example. Here also instead of declaring classes nested in test classes, the file restriction will help.

However keep in mind that a file restricted class won’t be visible from tests. Those must be considered as private implementation (black box) and must be tested through internal or public classes that access them.

Quick tip: You can test your internal implementations by tagging your projects with the System.Runtime.CompilerServices.InternalsVisibleToAttribute. This way you don’t have to declare these classes as public just for testing purposes.

What the compiler generates?

The C# compiler renames types with the file restriction this way: <SourceFileNameWithoutExtension>F$identfier$_TypeName. identfier is sometime an index incremented, or a random identifier, a bit like an UUID. This is just an implementation detail that is here to avoid type name collision in the context of the compiled assembly.

Thanks to the <> characters there is no risk for such class to be consumed from elsewhere than its file. This common compiler renaming pattern is called special naming and is used in a number of scenario including async/await , lambda, anonymous method, anonymous type.

Interestingly enough, when analyzing a bunch of projects within a solution, NDepend doesn’t try to change special names. Thus the NDepend dependency graph of the small program shown in the first screenshot above looks like this.

[ Edit 10th November 2022: Now NDepend v2022.2.1 and onward show the C# type name -here it is Answer– and provide the new  property IType.IsVisibleOnlyInFile to detect this file situation in code queries and code rules ]

C# 11 File Scope Type Decompiled

Again another great C# featurette that will help us write even cleaner code :))

 

My dad being an early programmer in the 70's, I have been fortunate to switch from playing with Lego, to program my own micro-games, when I was still a kid. Since then I never stop programming.

I graduated in Mathematics and Software engineering. After a decade of C++ programming and consultancy, I got interested in the brand new .NET platform in 2002. I had the chance to write the best-seller book (in French) on .NET and C#, published by O'Reilly and also did manage some academic and professional courses on the platform and C#.

Over my consulting years I built an expertise about the architecture, the evolution and the maintenance challenges of large & complex real-world applications. It seemed like the spaghetti & entangled monolithic legacy concerned every sufficiently large team. As a consequence, I got interested in static code analysis and started the project NDepend in 2004.

Nowadays NDepend is a full-fledged Independent Software Vendor (ISV). With more than 12.000 client companies, including many of the Fortune 500 ones, NDepend offers deeper insight and full control on their application to a wide range of professional users around the world.

I live with my wife and our twin kids Léna and Paul in the beautiful island of Mauritius in the Indian Ocean.

Comments:

  1. Worst feature ever.. Who the hell came up with the idea that it would be nice to be able to create multiple classes with the same name and same namespaces. Just terrible.. Why add stuff like this, nobody wants it
    And those edge cases explained above explaining why you would need them can and should be fixed in another way..

  2. To expand/extend a programming language it is essential to know it very well… Not only knowing “what can be done”, but **why the language was made the way it was made**: what is the purpose of each feature or each restriction.
    Allowing classes with the same “namespace.classname” in different files has created a problem where there was none and this does not solve any existing problem.
    Without a doubt, of the various nonsense that Microsoft is committing when evolving C#, this has been the worst!

Leave a Reply

Your email address will not be published. Required fields are marked *