Sunday, June 23, 2024 21:49

Table of contents >> Objects > Static members

Static members

As we saw in the previous recent lessons, the usual way of communicating with a class is to create instances (copies) of it, and then use the resulting objects. In fact, that is the strong advantage of the classes – the ability to create copies that can be used and can be modified individually. However, there are cases when you may want to use a class without instantiating it, or to access its members without creating an object for them. These special kind of members that are associated with the data-type (the class) and not with the specific instances, are called static members (static – because they remain the same).

So, as usually, I offered you the “academic” definition of what static means. Now, let’s explain in common words. What if, lets say, we have a class called Book, we are selling it, and we would like to know how many copies we sold? Our first thought would be to create something like this:

So, we have our class Book, in which we declare a private field called copies, which will store internally our copies value, and a public property called Copies, which we use to set the number of copies we sell. Finally, in the constructor of our class, we increment our Copies property by 1. This is so that we increment our Copies property every time we instantiate our class, meaning every time we create a copy of our book, to sell it. Now, lets see how this works:

Since we created three instances of our Book, we would expect that our Copies property should have the value 3. Sadly, this would be the actual output:

wrong static

So, if we incremented the value of our property each time we instantiated our class, why is it always displaying the value 1? Shouldn’t it display 1, 2, 3, etc?

Actually, no. From the lesson Instantiation we learned that when we are instantiating an object, we are actually creating individual copies of our class, which are separated one from another. This means that the Copies property in our firstBook instance IS NOT the same Copies property from secondBook or thirdBook instances. Each time we instantiated our class, we actually created a new, individual and separated Colors property, which has no idea about the existence of the previous copies and their values. It is of no use that we are incrementing it in the constructor of our class – so, each time we create a copy – because we are actually incrementing a new Copies property, each time initialized with value 0!

So, what can we do? How can we create a property that has its value independent of any instantiation, and would preserve the previous values, even when we create a new copy of our object?

This is where static members come handy. So, lets try this:

and then we display the results:

And, voilà! Here is the output:

static members output

We achieved our goal, we now keep track of the number of sold copies correctly, but WHY is it working now? Isn’t it creating a new Copies property each time we creating an instance, like before? Shouldn’t it have the default initialization value of 0 each time?

That is the beauty of static members. Each time we declare a field, property, method, etc with the static keyword, we are instructing the compiler NOT to create a copy of that element, but to create a unique one that is available and stays the same regardless of the instances we create. So, in other words, because we declared our Copies property as static, the compiler created a single, unique property, which remained the same for all of our three instances. Each of our instances did not create a copy of our Copies property when we created them, but rather all pointed back to the same unique property. This means that when we incremented Copies in the constructors, we incremented our unique property, which, being unique, retained the previous values. To better understand this, I will resort to my blueprint example again. I said few lessons ago that a class can be viewed as a blueprint, while instances are just copies created out of that blueprint. In our Book example, we are not selling the original manuscript – the Book class itself, but we create copies of it named instances. Static members are members that do not belong to the instances (copies), but to the manuscript, the blueprint, the original class itself. That’s why, no matter how many copies we create, it remains the same, in other words, static.

You probably observed in the second example that the syntax and the way we display the output was slightly different than in our first example. This is because static members, being independent of any instance, require special treatment. I will exemplify this further down in this lesson.

Static fields, methods, functions, properties are declared static usually by declaring their access modifier followed by the static keyword and the type, before their name:

If there is no access modifier specified, the declaration starts directly using the static keyword.

From the above example, we observe that we also declared our copies field as static, and inside our Copies property we did not use the this keyword. This is because a static element cannot access non-static class level members. It has no “this” pointer. In simple words, since our static elements are unique, and since this refers to the specific instance we are using at that moment, they are not compatible. You cannot have a static (unique) element access an instantiated (copied) element. If we tried to use a non static field inside a static property, like this:

we would have got a compiler error: An object reference is required for the non-static field, method, or property ‘Book.copies’.

Also, if we tried to use the this keyword inside our property, like this:

the compiler would have displayed two errors: Keyword ‘this’ is not valid in a static property, static method, or static field initializer and ‘Book.copies’ cannot be accessed with an instance reference; qualify it with a type name instead.

This is because, as I explained, a static (unique) element cannot deal with instantiated (copied, non-unique) elements. On the other hand, static elements are perfectly accessible from non-static elements. This means that any instance (copy) element of a class has access to and can modify a static (unique) element of that class. That’s why we can increment our static Copies property inside our non-static Book() constructor.

Aside of that, in our example, we accessed our Copies property like this:

You can clearly see that though we instantiated our Book class as an object called firstBook, we did not access the Copies property through that instance, like this:

If we did that, we would get a compiler error: ‘Book.Copies’ cannot be accessed with an instance reference; qualify it with a type name instead. You can already intuit that this happens because since static elements are unique, they belong to the original class, not to the instances of it. That’s the reason why we access static elements by using directly the class itself. So, this also means that we can access a static field without even creating an instance of that class:

The above code would display 0 on the console, because we initialized our copies static field as 0, and the static property Copies returned that value. Since we did not instantiate our class, there was no constructor call to increment it. However, we can increment it externally:

And the console would display 1 now.

One thing to notice here: even if non-static elements can access static ones, we still need to use instantiation when accessing them:

You should know that static elements are slightly more faster and efficient than non-static elements. This is because the compiler only creates them once in the memory, unlike instantiated elements, which are re-created every time we instantiate. However, you should not rely on this solely to “make your program faster”. The performance improvement is most of the times negligible.

We cannot declare static elements inside methods, functions, properties, constructors:

because the compiler would complain: The modifier ‘static’ is not valid for this item. Since currentPage is a local variable, which only lives inside TurnPage() method, which is a static method, the compiler knows the only option available is for currentPage to also be static. That is why we do not need to specify that it is static in its declaration.

Another thing of which you should be aware is that classes can be static too. In this case, they cannot be instantiated, and they cannot have a constructor. So, this:

would give a compiler error: Static classes cannot have instance constructors. So, we need to modify our constructor to make it static too:

But this would throw another error: ‘Book.Book()’: access modifiers are not allowed on static constructors. This indicates to us that the correct way of creating a static class with a constructor is like this:

Static constructors are slower than most constructors. Also they cause problems. They are lazily instantiated. Every access to the class must check that the static constructor has ran. So, as much as possible, avoid static constructors.

You may ask yourself where is the constructor called, since we never instantiate our static class. A static constructor is called automatically before the first instance is created or any static members are referenced. It is used to initialize any static data, or to perform a particular action that needs to be performed once only. A static constructor is also called type initializer.

Once we declare a static class, we cannot instantiate it anymore:

Since it will only give us errors: Cannot declare a variable of static type ‘Book’ and Cannot create an instance of the static class ‘Book’.

Since static members cannot access non-static elements, if we declare a static class, all the properties, fields, methods and functions of that class must also be static. We cannot have this:

because of the following error: ‘Book.currentPage’: cannot declare instance members in a static class.

A common usage of static classes, although frowned upon by some people, are what programmers call utility/helper classes, where you collect a bunch of useful methods, which might not belong together, but doesn’t really seem to fit elsewhere either. For instance, you can have a class called FormattingUtilities where you have different methods that you use throughout your programs all the time, and don’t need to be instantiated: FormatPhoneNumber(), FormatCountryCode(), FormatDecimalSeparator(), FormatDateTime(), etc.

Finally, to conclude this lesson, between the static classes which cannot be instantiated and the regular classes that can only be instantiated, there is a special kind of programming concept called singleton, which is actually a class that can be instantiated, but only once:

The above code may seem a bit complex, since we are using a few elements of which we haven’t learned yet (sealed, readonly), but if you ignore them, the logic becomes clear. For the sake of the argument, lets say that it is crucial that you use them: readonly ensures thread safety, while sealed allows the compiler to perform special optimizations during JIT compilation. The final methods above are the private instance constructor and an Initialize method. Private constructors mean the class can only allocate itself.

So, to help you understand why the SingletonClass class can only be instantiated once: we have a readonly property called Instance which can only be read, not written. Notice that the property is static, which means we cannot access it through instances. Also notice that the type of the property is the type of the class itself. This means that the property can only return a value of the class (SingletonClass) type. And this is true, since the getter returns the field _instance, which was declared of type SingletonClass, as a new instance of the class. The fact that the property is readonly, static and of type SingletonClass means that whenever we have this code:

we are actually creating internally a one time instance of our SingletonClass. Since the constructor of SingletonClass is private, we cannot instantiate the class, but also since we create internally a single instance and return it through a readonly property, we obtain a class that can only be instantiated once.

Tags: , , , , ,

Leave a Reply

Follow the white rabbit