Updated on

The using static directive lets us use a type’s static members, constants, enum values and nested types without writing the type name each time. It arrived in C# 6, and C# 10 added a global form that covers the whole project.

It doesn’t import everything, though. The base class and extension method sections below show what stays out, and the ambiguity section shows what happens when two imports share a name.

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

Let’s dive in.

What Does the using static Directive Import?

The using static directive imports the static members and nested types of one type into a file, so we can call them by their simple names. Instead of Math.Sqrt(16), we write Sqrt(16).

It works on any type that has static members: a static class, a regular class, a struct or an enum. Static methods, properties, fields, constants and enum values all become available without the type name. Instance members don’t, because they still need an object to be called on.

Members a type inherits from its base class aren’t imported. Extension methods become available on the type they extend, but we can’t call them as plain methods.

The feature arrived in C# 6. C# 10 added global using static, which applies the same import to every file in the project. We’ll go through each of these cases below, with code from our sample.

Its syntax is a single line:

using static <fully-qualified-type-name>;

Once we name a type with the using static directive, we can reference the static members and nested types of the <fully-qualified-type-name> without using its name.

Let’s look at how we can access the different types of static members with the using static directive.

Another great “using” directive usage is the “global using” directive so make sure to check our article about Global Using Directives in C# if you’re interested.

Methods and Properties

Let’s create a static class ClassA with two static methods MethodA() and MethodB():

public static class ClassA
{
    public static void MethodA() { }
    public static void MethodB() { }
}

In order to invoke these methods, we usually call them by their class name:

ClassA.MethodA();
ClassA.MethodB();

Here, we’re repeating the class name ClassA for invoking its static members every time. If we have to call the static members in many places, then the codebase will become too verbose.

But, with this feature, we can directly call the static methods without using their class name every time.

For that, we first need to name the type ClassA with the using static directive:

using static ClassA;

MethodA();
MethodB();

After that, we can directly call the static methods MethodA() and MethodB(). Similarly, we can access the static properties and fields.

Enums

Accessing the enum items directly without using the enum type name is also possible. Let’s create an enum Role with two items User and Admin:

public enum Role
{
    User,
    Admin
}

Usually, we would’ve accessed the enum items by their type name:

Role role = Role.User;

If we’re about to use the enum items in multiple places, then we can import the items with using static Role:

using static Role;

Role role = User;

Now, we can refer to the enum item User or any other without using its enum name.

Constants

In the same way, we can also directly access the constants. Let’s create a class Status with a few constants:

public class Status
{
    public const string Pending = "Pending";
    public const string InProgress = "InProgress";
    public const string Completed = "Completed";
}

To access these constants, we first need to name the type Status with the using static directive:

using static Status;

And then, we can directly access the constants Pending, Completed:

var pending = Pending;
var completed = Completed;

Nested Type Members

We can call the nested type’s static members in a similar way. Let’s create the nested type ClassB within ClassA. The nested type has two methods MethodA() and MethodB():

public class ClassA
{
    public class ClassB
    {
        public static void MethodA() { } 
        public static void MethodB() { }
    }
}

In order to call these methods, we usually end up calling them by prefixing the entire nested class hierarchy:

ClassA.ClassB.MethodA();
ClassA.ClassB.MethodB();

We can see that it is too verbose as we are repeating ClassA.ClassB every time to invoke the methods MethodA() and MethodB() of the nested class.

To overcome this, we can import the nested type members by the using static feature and call them directly. In order to do so, we first need to name the nested type ClassA.ClassB with the using static directive:

using static ClassA.ClassB;

MethodA();
MethodB();

After that, we can call the methods MethodA() and MethodB() directly.

Or else, we can only name the ClassA with using static directive and then access the ClassB methods as ClassB.MethodA() and ClassB.MethodB():

using static ClassA; 

ClassB.MethodA();
ClassB.MethodB();

Extension Methods

Extension methods are available for lookup if they are anywhere in the same namespace. But if it’s in a different namespace, we can name its type with the using static directive and start using the extension methods.

Let’s create a static class NumberExtensions with an extension method Add() that adds two numbers. It sits in its own namespace, UsingStaticInCSharp.ExtensionMethods.DifferentNamespace:

namespace UsingStaticInCSharp.ExtensionMethods.DifferentNamespace;
public static class NumberExtensions
{
    public static int Add(this int number1, int number2)
    {
        return number1 + number2;
    }
}

Once we name the class NumberExtensions with the using static directive, the extension method Add() is available on int in a different namespace, here UsingStaticInCSharp.ExtensionMethods:

using static UsingStaticInCSharp.ExtensionMethods.DifferentNamespace.NumberExtensions;

namespace UsingStaticInCSharp.ExtensionMethods;
public class Caller
{
    public virtual void Invoke()
    {
        var sum = 1.Add(2);
        Console.WriteLine("The sum is {0}", sum);
    }
}

When we run it, we get The sum is 3.

But it’s not possible to invoke it like a regular method using this feature:

DifferentNamespace.NumberExtensions.Add(1, 1); // Valid

// Error CS0103 
// The name 'Add' does not exist in the current context
Add(1, 1);

DifferentNamespace.NumberExtensions.Add() is a valid method call when called by its class name. However, it’s not possible to call the Add() method directly by using the using static directive. This applies only to extension methods.

Base Class Members

Using this feature, we cannot import the base class’s static members. Let’s create a base class ClassA with one static member MethodA() and a derived class ClassB with a static method MethodB(): 

public class ClassA
{
    public static void MethodA() { }
}

public class ClassB : ClassA
{
    public static void MethodB() { }
}

After importing the static members of ClassB, we can call the MethodB() without any errors.

But, if we try to call the base class method MethodA(), we get a compiler error:

using static ClassB;

// Valid
MethodB();

// Error CS0103
// The name 'MethodA' does not exist in the current context
MethodA();

Microsoft Learn says so in its using directive reference: “using static imports only accessible static members and nested types declared in the specified type. Inherited members aren’t imported.” So if we need MethodA(), we import ClassA as well, or call it as ClassA.MethodA().

Ambiguity Considerations With “using static” Directive

If we import static members of two different types with using static, we need to ensure that the method signatures are unique among those types. Otherwise, the compiler will throw an error if we try to access the method with the same signature.

Methods with the same name but different parameter lists don’t clash. The compiler treats them as one method group and picks the overload that fits the arguments, just as it would inside one class.

Let’s create two classes ClassA and ClassB with the same method signature void Method():

public class ClassA
{
    public static void Method() { }
}

public class ClassB
{
    public static void Method() { }
}

If we call this method when two of these classes are named with using static directive, then compile time error will be thrown:

using static ClassA;
using static ClassB;

// Error CS0121
// The call is ambiguous between the following methods or properties: 'ClassA.Method()' and 'ClassB.Method()'
Method();

Hence, we need to ensure that the method signatures are unique among the imported types.

How Do We Apply using static to Every File in a Project?

We add the global modifier. A global using static directive works like the regular one, but it applies to every file in the project, not only the file it sits in. C# 10 introduced it, and in its file it has to come before any non-global using directive and before any namespace or type declaration.

We can keep these lines together in one file, for example GlobalUsings.cs, so they’re easy to find. The project file can produce the same line through a <Using> item with Static="true", and the SDK writes the directive into a generated file at build time.

Every imported name is then in scope everywhere, including in files that never mention the type. When one of those files imports another type that has a member with the same name and signature, a call that compiled yesterday becomes ambiguous.

Let’s add one line to our project, in its own file:

global using static System.Math;

Now any file can call Math members by their short names, with no directive of its own:

namespace UsingStaticInCSharp.GlobalUsingStatic;
public class Caller
{
    public virtual void Invoke()
    {
        var hypotenuse = Sqrt(Pow(3, 2) + Pow(4, 2));
        Console.WriteLine("The hypotenuse is {0}", hypotenuse);
    }
}

This prints The hypotenuse is 5.

The same line can come from the project file instead:

<ItemGroup>
  <Using Include="System.Math" Static="true" />
</ItemGroup>

Let’s say the same file also imports a small Scores class with its own Max() method:

namespace UsingStaticInCSharp.GlobalUsingStatic;
public static class Scores
{
    public static int Max(int first, int second) => first > second ? first : second;
}

As soon as we call Max() without a type name, the build fails:

using static UsingStaticInCSharp.GlobalUsingStatic.Scores;

// Error CS0121 The call is ambiguous
// between the following methods or properties:
// 'System.Math.Max(int, int)' and 'UsingStaticInCSharp.GlobalUsingStatic.Scores.Max(int, int)'
var best = Max(3, 9);

The file never mentions Math. The clash comes from a directive in another file, which is why it takes a while to spot the first time. Writing Scores.Max(3, 9) fixes it.

The using directive gained one more trick in C# 12, when aliases started to accept any type, tuples included.

When Should We Avoid using static?

We avoid it when the type name tells the reader something. Sqrt(x) is clear without Math, and so is WriteLine() without Console. A call like TryParse(input, out var value) isn’t, because int, double and DateTime all have one, and the reader has to scroll up to the directives to find out which.

Name lookup is the other reason. When the class we’re in has its own member with the same name as an imported one, the compiler picks our member and says nothing. The using static line stays at the top of the file and looks like it does something.

So we keep using static for types whose members read well on their own: Math, Console, or a set of constants or enum values a file uses all the time. For other types we keep the type name, since it tells the reader where the member lives.

Let’s see the second case in code. ClassA has a static Describe() method:

namespace UsingStaticInCSharp.MemberPrecedence;
public static class ClassA
{
    public static string Describe() => "ClassA.Describe()";
}

Our Caller imports ClassA, but it also has a private Describe() of its own:

using static UsingStaticInCSharp.MemberPrecedence.ClassA;

namespace UsingStaticInCSharp.MemberPrecedence;
public class Caller
{
    public virtual void Invoke()
    {
        Console.WriteLine("Describe() returned {0}", Describe());
    }

    private static string Describe() => "Caller.Describe()";
}

When we run it, we get:

Describe() returned Caller.Describe()

The build has no errors and no warnings, and ClassA.Describe() never runs. Unlike the ambiguity case, the compiler doesn’t see a conflict here, because members of the enclosing class are found before the imported ones.

Conclusion

In this article, we’ve learned all about using static directive and their usage. This is a great feature and comes in handy when we want to reduce the verbosity of the frequently accessed static members.

Tested with .NET 10.0.10 and xUnit 2.9.3.