Practical .NET

Everything You Didn't Know About Shared and Static Members

Peter's convinced you're not using Shared and static members enough, but then he's also convinced that none of you know about all the ways you can use those two keywords.

The methods, properties and fields (class-level variables) that you normally add to your classes are actually just one kind of member: They're "instance members."

As the name suggests, an instance member belongs to the instance of the class of which it's a part -- the instance of the class that's created when you use the New keyword. The keywords this (in C#) and Me (in Visual Basic) refer to the instance of the class that your code is executing in and allow you to access instance members.

Class Members
But Me and this give you access only to your class' instance members. There's another class of members that you can't get to with Me and this because they belong to the class and not to any particular instance of the class: class members. Class members can only be accessed through the class name. But, unlike instance members, a developer doesn't have to instantiate a class to use a class member: Developers just type in the class name, a period, and the name of the method they want to call or property they want to set. For example, if a class has an instance method called CreateFile, a developer has to type this in Visual Basic to use the method:

Dim utils As Utilities
utils = New Utilities
utils.CreateFile()

In C#, the equivalent code looks like this:

Utilities utils;
utils = new Utilities();
utils.CreateFile();

If the CreateFile method is a class method, though, the equivalent code to call the method looks like this in Visual Basic:

Utilities.CreateFile()

If you use my original code to call the class member version of the method, Visual Basic will simply ignore the instance of the class and call the class member anyway (though you'll also get a warning message).

In C#, calling the class member version of the method looks like this:

Utilities.CreateFile();

As you might expect, C# is more anal retentive than Visual Basic and will only call a class member through the class name.

Obviously, class members make life easier for developers. Yet, I bet, you're not using class members as often as you could. That's odd because all you have to do to create a class member is to add one word to your member declarations: static in C#, Shared in Visual Basic. In fact, you probably have at least some experience in using class members: If you've ever used the Constant or const declaration then you have, effectively, been working with a class member. The static and Shared keywords just let you extend those benefits to other members of a class.

You probably have even numerous instance methods that you can convert to class members already available to you. If, for example, you have a method that doesn't call other members in the class or use any of the class fields then you could easily make it a class member by adding static or Shared to its declaration, simplifying life for any developer that uses that method. The methods that meet those criteria are typically the ones you think of as "utility" or "factory" methods (factory methods are methods that instantiate, configure and return an object).

Even if your method does access other members in the same class, you can, at least, consider whether all of the members involved can be made class members. If you can do that then you can still convert your instance method to a class method. In fact, you could make all the members of your class instance methods (C# makes that especially easy). The question now becomes "Do I ever need two of this class at the same time in the same application?" If the answer is "No," then you may not need any instance methods at all.

Both Visual Basic and C# offer features in class members that the other language does not. Visual Basic lets you declare class members within instance members; C# lets you declare a whole class as static. There's more going on here than most developers realize.

Limitations and Myths
Before going into the wonderfulness of class members, I should acknowledge their limitations. The primary limitation is that class members can only access other class members in the same class (class members can use instance members in any class they instantiate, of course). Class members can't be overridden: In derived classes, class members are inherited "as is" from the base class.

When it comes to limitations, however, there are also a bunch of myths associated with class members. For example, while class members do share values among all the instances of a class, those values are only shared with instances of the class running on the same application domain. You don't have to worry about setting a value in a class member in one application and having that value picked up by some other application that uses the same class. In many cases, the ability of class fields to remember a value between calls can actually be useful to you.

Contrary to popular belief, you can have a class constructor.

Generic classes can have class members, though you need to specify the type for the class when calling the class member. For example, if my previous example had used a generic class, then my code would've looked something like this in Visual Basic:

Utilities(of String).CreateFile()

The code is only slightly different in C#:

Utilities<String>.CreateFile()

One benefit associated with class members is also a myth, unfortunately: Class members don't run significantly faster than an equivalent instance member. Having said that, with a class member you do avoid whatever time it takes to instantiate the class containing the member, which will save you some time, depending on how much is going on with the class constructor and how long it takes to initialize any instance fields.

Creating Static Methods
Because the issues surrounding creating class methods are identical to the issues with creating class properties, I'll just look at creating class methods.

In C#, a class method looks like this:

public class Utilities
{
  static public void CreateFile()
  {
  }
}

In C#, if you have a class that consists of nothing but static members you can declare the class as static, which prevents developers from using the new keyword with the class to instantiate it:

public static class Utilities
{
  public static void CreateFile()
  {
  }
}

Static classes are severely limited when it comes to inheritance: A static class can't inherit from any other class, other than System.Object, and no class can inherit from it.

In Visual Basic, the code to define a static method looks like this:

Public Class Utilities
  Public Shared Sub CreateFile()
  
  End Sub
End Class

If you need to pass values between class methods, you can add class fields to your class (and wrap them in class properties, if you wish). If you're adding class fields to your class then you may want to initialize those fields from a class constructor (or perform any other initialization activities you want, as long as that code doesn't involve instance members):

public class Utilities
{
  static private string InitialFileName;

  static Utilities()
  {
    InitialFileName = "NewFile" + DateTime.Now;
  }

  static public void CreateFile()
  {
    ...code using InitialFileName...
  }
}

In Visual Basic, the equivalent code would look like this:

Public Class Utilities
  Private Shared InitialFileName As String = String.Empty

  Shared Sub New()
    InitialFileName = "NewFile"& DateTime.Now
  End Sub

  Public Shared Sub CreateFile()
    ...code using InitialFileName...
  End Sub
End Class

In both C# and Visual Basic, static constructors are always private -- you're not allowed to put any scope modified (private, Public) on them.

You have no control over when a class constructor will run, but you're guaranteed that it will run before you access either a class or instance member in the class for the first time. This is true even if you instantiate the class: The class constructor will run once before you access the first method or property and then will never run again for that class on that application domain. A class constructor can't accept parameters.

Class Fields
You do need to be careful with your class fields, however. Not only is the constructor only called once per application domain, any initialization that you build into a class field's declaration is performed just once for an application domain (it's performed before the class constructor executes). As a result, if you call a method that sets the value of a class field, the field will "remember" that value until the application domain terminates or the value is changed.

That can be an advantage. Here's some code that keeps track of the last set of values used to generate a filename and uses it to generate variations on the name:
Private Shared FileName As String = String.Empty
Private Shared Version As Integer = 0

Public Shared Sub GenerateVersion()
  Version += 1
  FileName &= "(" & Version.ToString & ")"
End Sub

In C#:

static private string FileName;
static private int Version;

static public void GenerateVersionedFile()
{
  Version += 1;
  FileName += "(" + Version.ToString() + ")";
}

However, if remembering the previous value isn't what you want then you have two choices. Your first, best choice is to not use a class field from your class methods. You can use non-class variables within a class method and those variables will be reset on each call to the method. The NewFileName field in this code will be reset each time the method is called:

static public void CreateDatedFile(string FileName)
{
  string NewFileName;
  NewFileName = FileName + DateTime.Now;

In Visual Basic:

Public Shared Sub CreateDatedFile(FileName As String)
  Dim NewFileName As String
  NewFileName = FileName & DateTime.Now

Your alternative is to reinitialize your class fields at the start of any method that uses the field.

Here's where Visual Basic has a useful feature that C# does not: Visual Basic lets you define what are, effectively, class variables within an instance method or property by using the Static keyword. These are variables that, like a class field, are initialized only once per application domain and remember their values between calls. However, unlike a class field, these variables can only be accessed within the method or property where they're declared. This example uses that feature to generate version numbers inside an instance method:

Public Sub CreateVersionedFile(FileName As String)
  Static Version As Integer
  Static OldFileName As String
  If FileName = OldFileName Then
   Version += 1
   FileName &= "(" & Version.ToString & ")"
  Else
    ...

As I said, there's a lot going on here and more opportunities to use class members in your code than you may realize. More important, exploiting those opportunities will make life easier for everyone who uses your classes.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube