In this article, we are going to look at the difference between static and non-static methods in C#.
In truth, there are many .NET languages beyond C# that offer these standard method types. And even beyond .NET we find that most object-oriented languages have some implementation of static and non-static methods. What is it that makes them such a compelling and ubiquitous language feature?
Classes With Static and Non-Static Methods
We’ll first cover the differences between the two, so that subsequent sections can continue to build on these basics.
A static method is a method that belongs to a class rather than an instance of a class.
That is the whole definition and it’s a simple one, but as with all definitions, it requires broader knowledge, so let’s look at our first example:
public partial class User { public string Email { get; set; } public string Password { get; set; } public bool CheckEmail() { var regex = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$"); var match = regex.Match(Email); return match.Success; } public static bool CheckPassword(string password) { return password.Any(c => char.IsUpper(c)) && password.Any(c => char.IsLower(c)) && password.Any(c => char.IsNumber(c)); } public bool CheckUser() { return CheckEmail() && CheckPassword(Password); } }
Here is a User
class with only two properties, Email
and Password
. Besides these properties, there are also three test methods, one for checking Email
, the second for checking Password
, and the last for checking the whole User
object.
Methods CheckEmail()
and CheckUser()
are instance class methods, but CheckPassword()
is a static
method. The first two methods can access any object property or method, so they can use the values of the local Email
and Password
properties. But CheckPassword()
can’t access those values since it belongs to the underlying class rather than the instance in which the property values exist. In this case, the values must be provided as parameters.
Let’s look at how we can use these three methods on an object of a type User
:
var user = new User() { Email = "[email protected]", Password = "ThisIs3asyPa$$word" }; Console.WriteLine($"Email is correct: { user.CheckEmail() }"); Console.WriteLine($"Password is correct: { User.CheckPassword(user.Password) }"); Console.WriteLine($"Whole object is correct: { user.CheckUser() }");
The CheckEmail()
and CheckUser()
methods are used as we are used to, as a method on the user
object. But the CheckPassword()
method can’t be accessed as a typical method on an object. Remember, it does not exist on an object; it exists on a type User
, so we need to use the type
and write User.CheckPassword()
. This method also expects a parameter, and we need to provide one. Because we are interested in the user’s password, we provide user.Password
as a parameter.
Why Can’t Static Methods Access Object Properties?
The real question we should ask is actually the reverse: “Why can instance methods access object properties?”
The answer is in the compiler. The .NET compiler secretly adds a special parameter this
to every object method, creating something similar to this behind the scenes:
public partial class User { public string Email { get; set; } public string Password { get; set; } public bool CheckEmail(User this) { ... } public static bool CheckPassword(string password) { ... } public bool CheckUser(User this) { ... } }
Note how every method gets a parameter of the type User
called this
, except the CheckPassword()
method as it is marked static
. To be clear, this is not a literal representation of what is happening behind the scenes, but it’s an accurate enough illustration for our purpose.
And we can indeed use a this.
prefix if desired:
public bool CheckEmail() { var regex = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$"); var match = regex.Match(this.Email); return match.Success; }
Here we see explicit access to the property Email
as this.Email
. Both versions are correct, and some companies even prefer or demand the second version of this.Email
for improved precision.
The this
keyword makes clear how instance methods access properties and why static methods cannot.
A Special Case To Consider In Program.cs
Looking closely at the code for this article in Program.cs
on GitHub, we find a method UsingUserObject()
:
UsingUserObject(); void UsingUserObject() { var user = new User() { Email = "[email protected]", Password = "ThisIs3asyPa$$word" }; Console.WriteLine($"Email is correct: { user.CheckEmail() }"); Console.WriteLine($"Password is correct: { User.CheckPassword(user.Password) }"); Console.WriteLine($"Whole object is correct: { user.CheckUser() }"); }
It seems like this method does not belong to any type, it doesn’t appear to be static
, and we are using it with no contextual type or object. What’s going on here?
Once again the compiler is doing its magic behind the scenes. In reality, this method is part of a compiler-created Program
class. It is actually a static
method, and it belongs to a Program
class, but that is all hidden from us by the compiler. This is an example of so-called top-level statements from C# 9.0, and this feature allows only one top-level file to be used in the project.
Inheritance and Method Overriding
We can’t override a static
method. But we can hide the implementation in the base object and define a new implementation in a derived object. Let’s look at a simple example of 2D objects:
public class TwoDimensionalObject { public virtual string Surface() { return "This object does not have a surface."; } public static string Description() { return "This is a basic 2D object"; } } public class Square : TwoDimensionalObject { public double SideLength { get; set; } public override string Surface() { var line1 = $"Base object's surface: {base.Surface()}"; var line2 = $"Square object's surface: {SideLength * SideLength}"; return $"{line1}{Environment.NewLine}{line2}"; } public new static string Description() { var line1 = $"Base object's description: {TwoDimensionalObject.Description()}"; var line2 = $"Square object's description: This is a square"; return $"{line1}{Environment.NewLine}{line2}"; } }
Here we have a basic 2D object and a derived object Square
. Each 2D object has a surface, so the base class defines a virtual
method Surface()
and each derived class will define its own implementation. The base class also defines a static
method Description()
. This method can not be virtual.
Next we define a Square
class that inherits from the TwoDimensionalObject
class. This class can override the Surface()
method, but can not override the Description()
method as it is not virtual. To access a base class virtual method, we use the base.
prefix, but as the static method is not virtual, we can’t use that approach. We have to define a new Description()
method and access the base class version using the full class name: TwoDimensionalObject.Description()
.
Method Overloading
We can’t override a static method, but we can overload it. It is perfectly acceptable to have two or more static methods with the same name as long as they are using different sets of parameters:
public class Dog { public string Name { get; set; } public static void Bark() { Console.WriteLine("Woof"); } public static void Bark(int barkCount) { for (int i = 0; i < Math.Min(20, barkCount); i++) { Console.WriteLine("Woof"); } } }
Here we define two static methods where the second overloads the first one with a parameter indicating how many times the dog barks.
Static Properties
We’re focused on static methods, but we should at least mention static properties in passing. As you might expect, these are properties of an underlying class and not of an object. For example, if we have a static property Count
on a Dog
class we can use it to count the number of different dogs we create in a program.
Performance Considerations
On the internet, we can find a wealth of information indicating that static methods are faster than non-static ones. This is theoretically true, but often unnoticeable in reality, and performance is typically not a significant enough factor to warrant the use of static
methods on its own.
Why Use Static Methods?
At this point, we might start asking ourselves why we would even bother to utilize static methods. We’ve seen that non-static methods can access properties and are easier to use, right? Is there even a good use case back in our first example? Consider this: To use a method CheckEmail()
we have to first create an object of type User
and then set an Email
property. But for checking a password, no object instantiation is required. We can call a method User.CheckPassword()
and check the provided password in a single line of code.
General Usage Rule
Any method that doesn’t change any state or require anything about an object to do its work is a good candidate to be made static. But in that case, we should also ask ourselves if the method even belongs within the class. Does it prevent the class from adhering to the Single Responsibility Principle?
That is a good question, and it is not always easy to answer. It may help if we answer two further questions. First, is the method related to this class? And second, is the method name with a class name more informational than just a method name without one? We ask the second question because we can’t call a static method without a class name.
If the method can’t be thought of as related to the class in any way, then it does not belong within it. And if a class name before the method name doesn’t add any useful context, then such a method might belong in a more general or utility class.
A Few Examples
Let’s look at the String class to explore some examples. Methods like Trim()
, ToUpper()
or Split()
are non-static methods because they access the internal state of the object even though they don’t change it.
But the Compare()
method, on the other hand, is a typical static method. Compare()
operates on two separate strings passed in as arguments, which are the only things needed to perform the comparison. Also, the Compare()
method should clearly belong to a class String
; otherwise it’s not obvious what we are comparing. A Date
class can also have a method called Compare()
, and again, its relationship to the underlying class is important!
Math class is another perfect example of a class with static methods. We used the Math.Min()
method in our previous example and it represents a perfect candidate for a static method. It receives two parameters and returns the smaller of them. This method does not need anything from the outside world, so it is static. It turns out that all methods of the Math
class are static, which actually allows the entire class to be made static as well.
The second great example of static methods is builder methods used in the Factory pattern:
public class Square : TwoDimensionalObject { public double SideLength { get; set; } public static Square Create(double sideLength) { return new Square() { SideLength = sideLength }; } public override string Surface() { // same as before } public new static string Description() { // same as before } }
Here we define a new static
method that builds and returns a Square
object. With our factory method, we can now create Square
objects:
var smallSquare = Square.Create(3);
Compare the simpler factory method to a more traditional object initializer style invoked via the new
operator:
var smallSquare = new Square() { SideLength = 3 };
Another good example of using a static method is a Singleton pattern, where we obtain an object by using a static
property of a Singelton
class, usually named Instance
.
There are a few good examples also in our article Static Members, Constants and Extension Methods
Conclusion
In this article, we learned about the difference between static and non-static methods in C#.
If you remember one rule of thumb regarding the proper usage of a static method, remember to use it if a method does relate to a class but does not need to change it!