54. Static methods#

Sometimes, we need to perform operations that don’t rely on the state of an individual object but that are universally applicable. Static methods fulfill this role. Like static fields and static constructors they belong to the class itself, not any individual object of that class. This means we can call them without creating an instance of the class.

Warning

Remember that using static is counter to the core ideas in object oriented programming, like subtype polymorphism, and can make your code less flexible and maintainable. Because static methods don’t work on instances, they can’t be overridden or used polymorphically. Use static methods judiciously and remember that there’s always an object oriented design that could meet your needs.

You’ve actually already been using static methods throughout this book without necessarily realizing it. The Console.WriteLine method, which we’ve used for outputting information to the console, is a static method. Here’s a quick reminder of its use:

Console.WriteLine("Hello, World!");
Hello, World!

In this case, WriteLine is a static method belonging to the Console class. We don’t have to create an instance of Console to use WriteLine; instead, we call it directly on the class.

The .NET Framework Class Library (FCL) provides a large number of useful static methods that you can use in your applications. For instance, the Math class, which provides methods and constants for trigonometric, logarithmic, and other mathematical functions. The Pow method is one example of a static method from this class. It raises a specified number to the power of another specified number:

double result = Math.Pow(2, 3);  // Computes: 2 ^ 3
Console.WriteLine(result);
8

In this code, Math.Pow is a static method that takes two arguments: the base and the exponent. It calculates the base raised to the power of the exponent and returns the result. In this case it calculates 2 to the power of 3.

Here’s another static method that you’ve proabbly been using without realizing it. The string interpolation feature in C# is essentially syntactic sugar for a static method call to String.Format. You might write code like:

string msg = "world";
Console.WriteLine($"Hello, {msg}!");
Hello, world!

What’s actually happening under the hood is something akin to:

string msg = "world";
Console.WriteLine(String.Format("Hello, {0}!", msg));
Hello, world!

As you can see, static methods are a key part of C# and .NET, and they allow us to use functionality without needing to instantiate an object. The key takeaway is that static methods are a fundamental part of the .NET library and are used in many places, even when it might not be immediately obvious.

Let’s now also write our own static method. Consider a simple game where players can score points. We might want to provide a way to calculate the average score across all players. This operation doesn’t depend on a particular player’s state, but rather on a static field representing the total points and total number of players.

Here’s an example:

public class Player
{
    public string Name { get; set; }
    public int Score { get; set; }

    private static int totalScore = 0;
    private static int totalPlayers = 0;

    public Player(string name)
    {
        Name = name;
        totalPlayers++;
    }

    public void AddScore(int score)
    {
        Score += score;
        totalScore += score;
    }

    public static float AverageScore()
    {
        if (totalPlayers == 0)
            return 0;
        else
            return (float)totalScore / totalPlayers;
    }
}

In this example, we have a static method called AverageScore that calculates and returns the average score of all players. Notice that it doesn’t make sense to tie this method to a specific Player instance, as the operation pertains to all players.

var player1 = new Player("Alice");
player1.AddScore(100);

var player2 = new Player("Bob");
player2.AddScore(200);

Console.WriteLine(Player.AverageScore());
150

Warning

Although it might be tempting to use static methods to perform operations that aren’t tied to specific instances, remember that these operations can still be modeled using an object oriented approach. In our Player example, we could create a Game class that keeps track of all Player instances. The GetTotalScore method could then be a part of the Game class, accessing the Score of each Player to compute the total. This approach adheres more closely to object oriented principles and can offer greater flexibility and maintainability in the long run.