Static variable, Shared variable... does it matter?
Introduction
Occasionally I am challenged with the task of needing to explain what the difference is between a "regular" variable and a "static" variable (called "Shared" in VB.NET). I use the term "challenged" not because the people I explain to aren't intelligent, but because it honestly is not a concept that is immediately clear for people that haven't had it explained to them before. Many people partially understand the concept of "object oriented" programming. But static variables are sort of like a step backwards from that which gets confusing. Anyway... last night I was working on a Severity A case (i.e. server is down... people are running around in a panic, thousands of dollars per hour are lost, CEO's are standing around looking for people to fire) where I found the root of the problem to be because of a static variable and I was having a tough time getting the concept across to the people I was helping. So I figured I'd see if I could come up with a good explanation here to save some time in the future.
So first... what is an "object"? If you don't know what an "object" is then you have no hope of understanding what a "static object" is.
You may have heard or read somewhere that "everything in .NET is an object." Okay great. But what the heck does that mean? Well, an object is basically like... I don't know... an object! Like a "real" object, as in a "noun". Maybe like a house. A house has parts in it that are always moving (refrigerator perhaps), things that just sit there (the couch), maybe has things that only do something when you turn them on or off (TV or faucet). So a house is an "object" that can have other objects in it. Or maybe it doesn't have other objects in it (house ready to be sold?). It all depends on the particular object that we've designed. Although "House" gives us a basic description of what this object is, there can be many different houses that are all still called "House". This is much the same as a programming object. It is basically a container of a particular format that can have other objects contained within it.
Okay, so let's make a "House" object in pseudo c# terms. It might look like this.
class House
{
string StreetAddress;
string ResidentName;
int ResidentCount;function TurnOn(string itemName)
{
if (itemName == "TV")
{
// Code to turn on the TV
}
else if (itemName == "Washer")
{
// Code to turn on the Washer
}
else
{
// Code to turn on all the Lights
}
}
}
Just the layout of a "House" object doesn't tell me a ton about any *specific* house. That's because this "class" is just a description of what a "House" should have. We can "create an instance" of one or more "House" objects like this:
House myHouse = new House();
House yourHouse = new House();
House dogHouse = new House();
The above code has setup a "new" section of memory that is used to store information about a particular House. Then we can perhaps set some of the values of each house:
myHouse.StreetAddress = "123 Street";
myHouse.ResidentName = "Brian Murphy-Booth";
myHouse.ResidentCount = 3;yourHouse.StreetAddress = "456 Another Street";
yourHouse.ResidentName = "Some Person";
yourHouse.ResidentCount = 5;dogHouse.StreetAddress = "Back Yard";
dogHouse.ResidentName = "Ollie";
dogHouse.ResidentCount = 1;
And finally... we can call our method to do something.
myHouse.TurnOn("lights");
yourHouse.TurnOn("TV");
As I go around and run the TurnOn("whatever") method of the different House objects that I have, those values affect only that specific "instance" of the "House" that I'm setting the value on. Each "House" has its own space in memory that has its own unique values. Therefore, calling myHouse.TurnOn("Lights") will only effect *my* house. Not yours. That's because there are 3 places in memory that holds an isolated "instance" of House.
Okay. We're almost ready to look at what a "static" object is. But before I do, let's fix up our pseudo class so it looks closer to valid C# syntax.
public class House
{
public string StreetAddress;
public string ResidentName;
public int ResidentCount;
private bool Initialized;public void TurnOn(string itemName)
{
if (itemName == "TV")
{
// Code to turn on the TV
}
else if (itemName == "Washer")
{
// Code to turn on the Washer
}
else
{
// Code to turn on all the Lights
}
}
}
You can see that all I've really done is added some "access modifiers" like "public" and "private" to control who can get or set the variable values. The behavior of setting "ResidentName" etc is essentially unchanged. The only difference now is that I've added an "Initialized" variable that can only be set by other methods inside of "House". For example... I cannot do:
myHouse.Initialized = true; // Can't do this
...since it is private. That's not really the important part of this Blog though, so let's move on. What if we "mistakenly" set one of our variables as "static"? Let's look at the following declarations that include that mistake.
public class House
{
public static string StreetAddress;
public string ResidentName;
public int ResidentCount;
private bool Initialized;... etc etc...
}
Uh oh... that's trouble. "What will happen?", you ask. Let's look again at our example of setting the values on the different House objects that we've created.
myHouse.StreetAddress = "123 Street";
myHouse.ResidentName = "Brian Murphy-Booth";
myHouse.ResidentCount = 3;yourHouse.StreetAddress = "456 Another Street";
yourHouse.ResidentName = "Some Person";
yourHouse.ResidentCount = 5;dogHouse.StreetAddress = "Back Yard";
dogHouse.ResidentName = "Ollie";
dogHouse.ResidentCount = 1;
The end result here would be that "myHouse.StreetAddress", "yourHouse.StreetAddress", and "dogHouse.StreetAddress" would all have a value of "Back Yard". That's because when a variable is "static" there is only ONE version of the StreetAddress variable in all of the memory!! It is no longer associated with some unique "instance" of House. Think of a "static" variable as being similar to calling the local police station. It doesn't matter which House you call them from. The same phone will ring in the police station up the road. Ahh... but if there is only *1* copy of that StreetAddress variable (no "instance" versions) then there is actually a coding mistake above. Attempting to compile the above code would generate a compile error. It can actually only be like this:
House.StreetAddress = "123 Street";
myHouse.ResidentName = "Brian Murphy-Booth";
myHouse.ResidentCount = 3;House.StreetAddress = "456 Another Street";
yourHouse.ResidentName = "Some Person";
yourHouse.ResidentCount = 5;House.StreetAddress = "Back Yard";
dogHouse.ResidentName = "Ollie";
dogHouse.ResidentCount = 1;
In reality, since making it "static" means there can only be one copy, StreetAddress cannot be accessed using an "instance" name anymore (such as "myHouse"). There is one and only one version of StreetAddress in all of memory so we just say which object type we're referring to (House is a "type" of object) then set it directly (House.StreetAddress).
Abbreviated Real World Example:
I'll walk you through what was happening with the customer that I was helping last night. The configuration was something like this:
- ShowInfo.aspx and ShowInfo.aspx.cs
- DataBaseStuff.cs.
- In DataBaseStuff there was a "static" SqlConnection *variable* called myConnection
- In DataBaseStuff there was a "static" SqlCommand *variable* called myCommand
- In DataBaseStuff there was a "static" *method* named GetDataTable().
- Page_Load was calling DataBaseStuff.GetDataTable(string userName) to get some data.
- GetDataTable made use of the static myConnection object to retrieve data from SQL.
- Page_Load assigned the resulting DataTable to a DataGrid.
- Personal information for the logged in user was displayed on the web page.
The result? During peak hours for the web site, information for UserA was being shown to UserB. How did this happen?
- ShowInfo.Page_Load() begins to execute for UserA.
- ShowInfo.Page_Load() begins to execute for UserB
- UserA has DataBaseStuff.GetDataTable("UserA") called for them.
- UserB has DataBaseStuff.GetDataTable("UserB") called for them.
- UserA has something like "SELECT * FROM Users WHERE username='UserA';" assigned to the static myCommand object.
- UserB has something like "SELECT * FROM Users WHERE username='UserB';" assigned to the static myCommand object.
- UserA starts to pull data from SQL.
- UserB starts to pull data from SQL.
- Result: Since UserB's SQL query was the last one assigned to myCommand (which remember... there is only one version of this in all of memory), the myConnection object is using UserB's query for both users. UserB sees UserB's data. UserA sees UserB's data too.
Here are some questions I asked my customer after finding the problem.
Me: Why are the myCommand and myConnection variables "static"?
Customer: Because there was a compile error in GetDataTable when they weren't static.
I think: Hmm... not the best of reasons. But okay.
Me: That's because GetDataTable is static. Why is the GetDataTable method "static"?
Customer: Because when it is static I don't have to use "new" when calling it from ShowInfo.aspx.
I think: Okay. That's a valid reason.
Me: Well... that's a good reason to make it static. But... [So then I try for 20 minutes to explain what a static variable is.]
*The* reason you want to make a variable static is for scalability reasons. In my customer's example, it would not make sense to user the same SqlConnection and SqlCommand for multiple users because of the types of problems it could cause. It would, however, make sense to use the same connection string for multiple users. If you had 100 instances of DataBaseStuff in memory, you would not want to waste space by having 100 copies of the same connection string assigned to all 100 versions of DataBaseStuff Instead, we'd make myConnectionString static so that it exists in memory only once (yes, I know what an "interned" string is. For all you experts: don't complicate my example!!).
Summary:
There are many different features of C# (and VB.net). Using "static" methods and variables can be a big plus relative to performance and scalability. The important caveat is just that you make sure you use the features in the way they were intended on being used! Doing something "just because" can cause unexpected results.
I'm interested in your feedback. If my explanation above doesn't make sense, let me know. Leave a comment with your email address and I'll see if there is something I can do to make it easier to understand. All comments must be approved by me first so I'll be sure not to publish your address.