C# Global Using Explained
c# global using: Learn how the C# global using directive works, where it applies, and when to prefer it over project-level implicit usings or explicit file-level imports.
The c# global using directive is a compiler feature introduced in C# 10 that lets you add a using directive to every source file in the project with a single declaration. Instead of repeating using System; or using System.Linq; at the top of each file, you place the directive once, typically in a dedicated file or in Program.cs. This can reduce clutter, but it also changes how you read code: a file no longer lists all the namespaces it depends on. That tradeoff matters when you're deciding whether to use global using for a new project or for an existing codebase.
How the global using Directive Works
A global using looks like a normal using directive with the global modifier placed before the keyword:
global using System; global using System.Collections.Generic;
After you add these lines to a project, every .cs file in that project can use types from System and System.Collections.Generic without an explicit using. The compiler treats the global directive as if it were present at the top of each file. You can place global usings in any file, but the convention is to keep them together so the project's external dependencies are easy to review.
The directive can also be used with static imports or aliases:
global using static System.Math; global using ProjectAlias = Some.Long.Namespace;
global using static brings all static members of a type into scope project-wide, while the alias form assigns a shorthand name to a namespace or type. Both features are useful when a large codebase references the same static helper or long namespace repeatedly.
The Scope of a Global Using
A global using applies to the entire compilation unit—that is, to every file that is part of the project. There is no way to restrict a global using to a subfolder or to a subset of files. If you need a using to apply only to certain files, you must use a regular using in each of those files.
Consider a solution with multiple projects. A global using in the OrderService project does not affect the PaymentService project. Each project has its own set of global usings. That is important when you share code across projects via assembly references: the referenced assembly's types are not automatically imported into the consuming project unless the consuming project also declares the appropriate global or local usings.
The compiler resolves global usings before local usings. This means a local using can override a global alias if the names conflict, but you should avoid relying on that behavior because it makes the resolution harder to follow.
Implicit Usings and the .NET SDK
Since .NET 6, the default project template enables implicit usings, which automatically adds a set of global usings based on the project type. For a console application, the SDK injects something like:
global using System; global using System.IO; global using System.Collections.Generic; // etc.
These implicit usings are generated at build time and are not visible as source files. They are the reason a minimal Program.cs from the template works without any explicit using directives. You can control this behavior with the <ImplicitUsings>enable</ImplicitUsings> property in the project file. If you set it to disable, the automatic global usings are removed, and you must either write explicit usings or add your own global usings.
Understanding the distinction is important because implicit usings are a convenience provided by the SDK, while global using is a language feature. The two often work together: the SDK uses global using internally to generate the common imports. If you want to add your own global usings, you can do so regardless of the ImplicitUsings setting.
Managing a Global Usings File
Many teams create a file named GlobalUsings.cs to hold all global using directives. This file typically contains only global using lines and nothing else. Keeping them in one place makes it easier to spot namespace changes and to review what the project depends on globally. For example, you might have:
// GlobalUsings.cs global using System; global using System.Collections.Generic; global using System.Linq; global using System.Threading.Tasks; global using FluentValidation;
When you add a new package that provides extension methods, you may need to add its namespace to this file so the methods are available everywhere. Failing to do so results in compile errors that often appear in many files, because the extension methods are not in scope.
Potential Merge Conflicts and Maintainability
A single global usings file centralizes a set of dependencies, but it also becomes a common target for edits. If several developers are adding packages at the same time, they may modify the same file and create merge conflicts. In contrast, local usings are scoped to the file that uses them, so parallel changes to different files rarely conflict.
There is also a readability cost. When you open a source file, you cannot see which namespaces it relies on unless you scroll to the top or check the project settings. For a file that uses DateTime, Regex, and CancellationToken, the origin of those types is not obvious. Tools like IDE navigation can resolve them, but the file itself does not tell the full story.
For large, maintenance-heavy codebases, many teams prefer explicit local usings over global ones because they make dependencies visible at the point of use. The global approach is more attractive for small projects or when the set of required namespaces is stable and well-known.
Compatibility and Build Considerations
The global using directive requires C# 10 or later. If your project targets an older language version, the compiler will report an error. When you upgrade a project to .NET 6 or newer, the default language version is C# 10, so the feature is available without extra configuration. For projects that target .NET Core 3.1, you can still use C# 10 in some cases by setting <LangVersion>10</LangVersion>, but the SDK may not support all features unless the runtime is compatible. In practice, use the feature only when your toolchain and target runtime both support it.
The feature has no runtime cost. The compiler resolves the usings at compile time, and the generated IL is identical to using regular usings. Global usings do not affect performance, memory usage, or the assembly's public surface. They are purely a source-level convenience.
When to Use Global Usings vs. File-Scoped Usings
There is no single correct choice. Use global usings when a namespace is used in the majority of files and the set of namespaces is small and stable. For example, a library that heavily uses System.Linq in every file benefits from a single global using. Use file-scoped usings when the namespace is only needed in a few files or when you want to keep the dependency list near the code that uses it.
A practical rule is to start with file-scoped usings and adopt global usings only when repetition becomes annoying and the set of namespaces is unlikely to change frequently. The decision also depends on team convention. If the team values explicit code, global usings may be a poor fit. If the team values concise files, the global approach is convenient.
The c# global using directive is a useful tool for reducing repetition, but it moves dependency information away from the code that uses it. Weigh that cost against the benefit of fewer lines. The choice is about maintainability, not about the compiler's behavior.