Handling Global State in C# Applications
When writing code, we often use mutable variables to store changing data. Consider this method for example:
public static void ProcessDocuments(string[] documentIds)
{
int countSuccess = 0;
int countFailedToFetch = 0;
int countFailedToTranslate = 0;
int countFailedToStoreInDatabase = 0;
foreach (var documentId in documentIds)
{
var processingResult = ProcessDocument(documentId);
if (processingResult == DocumentResult.Success)
countSuccess++;
else if (processingResult == DocumentResult.FailedToFetch)
countFailedToFetch++;
else if (processingResult == DocumentResult.FailedToTranslate)
countFailedToTranslate++;
else
countFailedToStoreInDatabase++;
}
string report =
$@"Processing complete.
Total Success: {countSuccess}
Total failed to fetch: {countFailedToFetch}
Total failed to translate: {countFailedToTranslate}
Total failed to store in database: {countFailedToStoreInDatabase}";
SendEmailToAdministrator(report);
}
The ProcessDocuments method goes through an array of document ids, and calls a method called ProcessDocument on each document id. The ProcessDocument method processes a single document. It obtains each document from some repository, translates it, and then stores it in some database.
The return value of the ProcessDocument method which is of type DocumentResult indicates whether the processing was successful or not. In case of failure, it indicates one of three kinds of failure.
Based on the return value of the ProcessDocument method, we increment one of the four counters declared in the code above. There is one counter for each possible value of DocumentResult. Such counters are used later to create a report that will be sent to the administrator via email.
These four counters hold state.
It is called state because the counters don’t keep the original values they are initialized with (0 in this case), but change as the method executes.
In this article, I am going to talk about state in two different scopes.
The first scope is the method scope. In this scope, mutable variables are defined, mutated, and used inside a single method.
The second scope is the multi-method scope. In this scope, mutable fields (or properties) can be defined in one place, mutated in another place (e.g. one method) and used in yet another place (e.g. another method).
I will start by talking about techniques for eliminating state in the method scope.

State in the method scope
The ProcessDocuments method I talked about earlier, is relatively readable. Because the counters are defined and used only inside this method, and because this method is relatively small, using state in this method is not really an issue.
Usually, state in the method scope doesn’t need to be eliminated.
Still, we can eliminate it if we want to.
Consider this method that uses LINQ’s Count method:
public static void ProcessDocumentsViaLinqCount(string[] documentIds)
{
var results = documentIds
.Select(id => ProcessDocument(id))
.ToList();
int countSuccess = results.Count(x => x == DocumentResult.Success);
int countFailedToFetch = results.Count(x => x == DocumentResult.FailedToFetch);
int countFailedToTranslate = results.Count(x => x == DocumentResult.FailedToTranslate);
int countFailedToStoreInDatabase = results.Count(x => x == DocumentResult.FailedToStoreInDatabase);
string report =
$@"Processing complete.
Total Success: {countSuccess}
Total failed to fetch: {countFailedToFetch}
Total failed to translate: {countFailedToTranslate}
Total failed to store in database: {countFailedToStoreInDatabase}";
SendEmailToAdministrator(report);
}
In this updated method, we use LINQ. We use the Select method to call the ProcessDocument method and obtain a DocumentResult for each document id. We call the ToList method to force execution and to put the results inside a List<DocumentResult>. For each possible value of DocumentResult, we define a count* variable to hold the corresponding number of documents.
To count the documents, we use the Count method. We use an overload of this method that takes a predicate. We use this predicate to specify which values to count.
Although we see no variables mutated inside the ProcessDocumentsViaLinqCount method, the Count method internally uses a mutable variable to keep track of the count. This is how the method looks like based on the source code of the .NET Framework version 4.7.2 (modified to remove code irrelevant to the discussion here):
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
int count = 0;
foreach (TSource element in source)
{
if (predicate(element)) count++;
}
return count;
}
The count variable is mutated every time the predicate returns true. So, by using the Count method, we simply move state from ProcessDocumentsViaLinqCount to Count.
Note: the ProcessDocumentsViaLinqCount is not very optimized when it comes to performance. There are ways to make it more performant while keeping it state-free. This is outside the scope of this article, however.
Recursion is another technique that can be used to eliminate method-scoped state. Consider this method for example that calculates the factorial of a number:
public static int Factorial(int number)
{
int factorial = 1;
for (int i = 2; i <= number; i++)
{
factorial = factorial * i;
}
return factorial;
}
There are two variables that get mutated in this method: factorial and i.
Here is how the recursive version looks like:
public static int FactorialRecursive(int number)
{
if (number <= 1)
return 1;
return number * FactorialRecursive(number - 1);
}
No variables are mutated in this version. It is easy to understand this version of the method if we see that n! = n * (n – 1) * (n-2) * … * 1 = n * (n -1)!
Let’s now talk about a more important type of state.
State in the multi-method scope
As I mentioned before, method-scoped state is usually not an issue and doesn’t need to be eliminated, especially if methods are small. It is state that is defined outside the scope of a single method that needs special treatment. Consider this example:
public static class GlobalServer1State
{
public static DateTime Server1DownSince { get; set; }
public static bool Server1IsDown { get; set; }
}
public static class GermanTextTranslationModule
{
public static Text TranslateFromGerman(Text text)
{
bool useServer1 = true;
if (GlobalServer1State.Server1IsDown)
{
if (DateTime.Now - GlobalServer1State.Server1DownSince < TimeSpan.FromMinutes(10))
{
useServer1 = false;
}
}
if (useServer1)
{
try
{
var result = TranslateFromGermanViaServer1(text);
GlobalServer1State.Server1IsDown = false;
return result;
}
catch
{
GlobalServer1State.Server1IsDown = true;
GlobalServer1State.Server1DownSince = DateTime.Now;
}
}
return TranslateFromGermanViaServer2(text);
}
//...
}
public static class SpanishTextTranslationModule
{
public static Text TranslateFromSpanish(Text text)
{
//Same logic as in TranslateFromGerman
//...
}
//...
}
The complete example is available in the UsingGlobalVariables project in the StateExamples solution. You can see this solution here: https://github.com/ymassad/StateExamples
Note that for simplicity, all the projects use dummy data instead of real data.
The TranslateFromGerman method takes some text as input and translates it from German to English using some remote servers.
Based on customer requirements, the application should use translation server 1 to translate. In case there are errors using server 1, server 2 should be used. Also, if there is an error with server 1, server 2 should be used for any translation requests for the next 10 minutes. Only after that should the application try to use server 1 again.
The Server1IsDown and Server1DownSince properties of the GlobalServer1State class are used to store the state required to make this work. When there is an error talking to server 1, the code sets Server1IsDown to true and Server1DownSince to the current time. The values of these two properties are used at each invocation of TranslateFromGerman to determine whether to use server 1 or server 2. After a successful call to server 1, Server1IsDown is set to false.
The TranslateFromSpanish method works in the same way as TranslateFromGerman. They both share the state stored in GlobalServer1State. If the application detects that server 1 is down when invoking TranslateFromGerman, TranslateFromSpanish would be smart enough to use server 2 if it was invoked within 10 minutes after that.
This code, however, has the following issues:
1) The behavior of TranslateFromGerman, for example, cannot be understood by reading this method alone. Whether this method will use server 1 or server 2 does not depend solely on the parameters passed to this method.
Previous invocations of TranslateFromGerman might affect how the method behaves. Also, invocations of TranslateFromSpanish can affect how TranslateFromGerman behaves. Other relevant methods might also be designed to change the Server1IsDown and Server1DownSince properties and thus affect the behavior of TranslateFromGerman. Also, the signature of TranslateFromGerman does not tell us that this method modifies state. This makes this method dishonest. See the Writing Honest Methods in C# article for more details.
2) When writing tests for TranslateFromGerman or for methods that call it directly or indirectly, we need to initialize the state properties correctly before running the tests so that the results will be predictable. It is easy to forget something like this. For example, the TranslateDocumentsInFolder method from the example project calls the TranslateFromGerman method indirectly (TranslateDocumentsInFolder > TranslateDocument > TranslateParagraph > TranslateText > TranslateFromGerman).
In a large application that uses global variables to store state, it would be hard to figure out which state does a method like TranslateDocumentsInFolder depend on indirectly.
3) What starts as global state might be required later to become scoped state. For example, consider the following new changes:
- Now there are two servers (1 and 2) in location A, and another two servers (1 and 2) in location B.
- Documents in folder 1 should be translated using servers in location A, and documents in folder 2 should be translated using servers in location B.
- The state of servers in location A is different from the state of servers in location B. This means that when server 1 in location A is down, translation of documents from folder 2 should continue to use server 1 in location B.
It doesn’t make sense to duplicate the code in TranslateFromGerman and TranslateFromSpanish(and other methods that call them) so that each copy of these methods (a copy for documents in folder 1 and another copy for documents in folder 2) uses a different global state object. It is much easier to reuse all of these methods. This means that we cannot simply use global state here.
There is a solution that addresses all of these issues. Consider this updated method signature:
public static Text TranslateFromGerman(Text text, ref Server1State server1State)
public class Server1State
{
public DateTime Server1DownSince { get; }
public bool Server1IsDown { get; }
//Constructor..
}
Notice the additional Server1State parameter added to the TranslateFromGerman method. This parameter is passed by reference. This means that this parameter allows the caller to give this method a Server1State value, but also the TranslateFromGerman method can give the caller back an updated value of this parameter.
See the PassingStateViaRefParameters project in the sample code
The following are differences between this and the global state solution:
1) The signature of the new TranslateFromGerman method indicates to the reader of the code that this method both reads and writes state. This makes it easier to understand how this method works.
2) When testing, we must provide a value for the server1State parameter or the test will not compile. This will fix the issue of forgetting to initialize the state when writing tests.
3) Different components can use this method passing different state variables for the server1State parameter. This allows the reuse of this method.
Note that the Server1State class is immutable, that is, once an instance is created, the value of its properties cannot change. When a method like TranslateFromGerman wants to update the state, it creates a new instance of Server1State with updated values of the properties and assigns it to the server1State parameter.
We could make the Server1State class mutable and make the server1State parameter a normal parameter instead of a ref parameter. However, the issue with this approach is that the method signature wouldn’t tell us that such state is mutable. Using the ref keyword with an immutable state class makes it very clear to the reader of the code that this parameter represents something that the method can change.
Now, if we can simply use this solution to fix the issues introduced by using global variables, why do so many people use global variables?
Take a look at the PassingStateViaRefParameters project. Start from the Main method where the TranslateDocumentsInFolder method is called. This method calls TranslateFromGerman indirectly like this: TranslateDocumentsInFolder > TranslateDocument > TranslateParagraph > TranslateText -> TranslateFromGerman.
Notice how I had to add a server1State parameter to all of these methods. Currently, only TranslateFromGerman and TranslateFromSpanish really require these parameters. But because we want to get rid of global variables, many methods have to have this parameter as well. This is important because when TranslateDocumentsInFolder ends up invoking TranslateFromGerman and TranslateFromSpanish multiple times, we need to make sure that all of these invocations get a reference to a single state object.
So basically, we polluted the entire call hierarchy with an extra parameter that does not make sense to all methods. Why should TranslateDocument, for example, care about the state of server 1?
What happens when a lower-level method requires some new state parameter? We have to update all methods that call it directly or indirectly to take and pass this new parameter. What is the solution?
The solution is dependency injection.
Using Dependency Injection
Dependency injection can be done using classes or using functions. There is not a conceptual difference between doing dependency injection using classes or functions. In a previous article, Composing Honest Methods in C#, I proved this by solving the same problem once using dependency injection with classes, and the second time using functions.
In this article, I will concentrate only on functions.
Dependency injection allows us to specify some parameter value of a function at composition time so that the function only requires the rest of its parameters when invoked at runtime.
For dependency injection to work, we need to invert control. For example, the TranslateDocumentsInFolder method would not call the TranslateDocument method directly. Instead, it would receive a function parameter that it calls instead.
public static void TranslateDocumentsInFolder(
string folderPath,
string destinationFolderPath,
Func<Document, Document> translateDocument)
{
IEnumerable<Document> documentsEnumerable = GetDocumentsFromFolder(folderPath);
foreach (var document in documentsEnumerable)
{
var translatedDocument = translateDocument(document);
WriteDocumentToDestinationFolder(translatedDocument, destinationFolderPath);
}
}
Notice the translateDocument function parameter. It does not take any state parameters. Also, the TranslateDocumentsInFolder method does not take any state parameter.
See the PassingStateViaRefParametersWithIOC project. It contains the full source code. Take a look at all the relevant methods. Many methods take function parameters instead of invoking lower-level methods directly.
Here is an excerpt from the Composition Root (the Main method):
Server1State server1StateForLocationA = new Server1State(false, DateTime.MinValue);
FolderProcessingModule.TranslateDocumentsInFolder(
"c:\\inputFolder1",
"c:\\outputFolder1",
document => DocumentTranslationModule.TranslateDocument(
document,
paragraph => DocumentTranslationModule.TranslateParagraph(
paragraph,
paragraphText => DocumentTranslationModule.TranslateText(
paragraphText,
text => GermanTextTranslationModule.TranslateFromGerman(
text,
Location.A,
ref server1StateForLocationA),
text => SpanishTextTranslationModule.TranslateFromSpanish(
text,
Location.A,
ref server1StateForLocationA)))));
In this project, I use lambda expressions as a mean to compose the functions together. Note that only the TranslateFromGerman and TranslateFromSpanish methods have a ref parameter of type Server1State. The other methods don’t know about the existence of such state.
We can define state instances as variables in the Main method, and then pass them by reference only to the functions that really need them. Note also that the TranslateFromGerman and TranslateFromSpanish methods were updated to take a Location parameter. This parameter determines whether to use servers in location A or B. In the excerpt above, the code uses location A. Note also, that this code uses a variable named server1StateForLocationA to store the state.
In the Main method, there is also similar code that processes documents in the c:\inputFolder2 folder and uses servers in location B. This code uses another variable, i.e. server1StateForLocationB, to store the state.
The solution we reached so far is far from perfect.
When the application becomes bigger, the lambda composition block becomes larger. Look at the lambda composition block above. Notice how indentation becomes longer as we compose the lower-level functions. Also, it is very hard to understand a large single block like this. It would be better if we extract each composed function into its own variable like this:
Func<Text, Text> translateFromGerman = text =>
GermanTextTranslationModule.TranslateFromGerman(
text,
Location.A,
ref server1StateForLocationA);
Func<Text, Text> translateFromSpanish = text =>
SpanishTextTranslationModule.TranslateFromSpanish(
text,
Location.A,
ref server1StateForLocationA);
Func<Text, Text> translateText = paragraphText =>
DocumentTranslationModule.TranslateText(
paragraphText,
translateFromGerman,
translateFromSpanish);
Func<Paragraph, Paragraph> translateParagraph = paragraph =>
DocumentTranslationModule.TranslateParagraph(
paragraph,
translateText);
Func<Document, Document> translateDocument = document =>
DocumentTranslationModule.TranslateDocument(
document,
translateParagraph);
This fixes the indentation issue and makes it easier to individually understand each composed function. However, this brings up other issues. I will talk about these issues and propose a solution in an upcoming article.
There is more to dealing with state than what is covered in this part. For example, are there alternatives to ref parameters? How to deal with state in multi-threaded applications? I will talk about these topics in another part.
Conclusion:
In this article, I talked about state.
State represents data that changes. When we have a variable in a method that changes its value as the method executes, we say that such variable holds state. State that is scoped to a single method is generally not a problem, and no special care should be given to such state. When multiple methods are required to share state, people tend to use global variables/objects to store such state. Such usage of global variables makes it hard to understand, test, and reuse code that uses such variables.
To solve such issues, we can make methods take the state using parameters. This makes it easier to understand, test, and reuse such methods. However, this means that all methods that directly or indirectly use these methods might be required to take and pass such parameters themselves.
To fix that, we can invert control and use dependency injection. In this case, functions don’t call each other directly but call function parameters passed to them. The functions that don’t need to deal with state directly don’t have to take and pass any state parameters.
However, someone must connect these functions together. I showed an example in this article that uses lambdas to do just that. Only the functions that directly need to deal with the state are given a reference to the state.
There is still more to talk about regarding state. I will talk about this topic more in the next part(s).
This article was technically reviewed by Damir Arh.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Yacoub Massad is a software architect and works mainly on Microsoft technologies. Currently, he works at NextgenID where he uses C#, .NET, and other technologies to create identity solutions. He is interested in learning and writing about software design principles that aim at creating maintainable software. You can view his blog posts at
criticalsoftwareblog.com. He is also the creator of DIVEX (
https://divex.dev), a dependency injection tool that allows you to compose objects and functions in C# in a way that makes your code more maintainable. Recently he started a
YouTube channel about Roslyn, the .NET compiler. You can follow him on twitter @
yacoubmassad.