Not only do AVR for .NET shared class members generate a lot of questions for us in ASNA tech support, but their misuse cause AVR for .NET programmers grief and consternation. Let's a look at exactly what shared members are and when to use them.
Consider the need for a set of reusable math functions (beyond those that the System.Math namespace already provides). In our case, we need a Squared
method which returns the squared value of an 32-bit integer (an academic example indeed, but it works for our purposes!).
A way to write and use the Squared
method is shown below in Figure 1:
DclFld MyMath Type(MathFunctions) New()
DclFld Result Type(*Integer4)
Result = MyMath.Squared(5)
...
BegClass MathFunctions Access(*Public)
BegFunc Squared Access(*Public) Type(*Integer4)
DclSrParm x Type(*Integer4)
LeaveSr x * x
EndFunc
EndClass
Figure 1. The Squared function as an instance member.
In the Figure 1 example, Squared
is an instance member of the MathFunctions
class. Instance members belong to class instances, not to the class itself. In this example, Squared
belongs to the MyMath
instance of the MathFunctions
class.
In this example, consider the attributes of the Squared
function:
-
Its return value is guaranteed to be same, every time called, with a given argument value. That is, if you call it six times with the argument
5
each call returns the value25
. -
The
Squared
function has zero side effects. It doesn't change program state in any way. TheSquared
function is a pure black box, give it an input and it returns the same output every time.
A function meeting these two attributes is known as a pure function.
. Pure functions are superb candidates for being shared members. Consider the code below in Figure 2.
DclFld Result Type(*Integer4)
Result = MathFunctions.Squared(5)
...
BegClass MathFunctions Access(*Public)
BegFunc Squared Access(*Public) Type(*Integer4) Shared(*Yes)
DclSrParm x Type(*Integer4)
LeaveSr x * x
EndFunc
EndClass
Figure 2. The Squared function as a shared member.
In Figure 2 above, the Squared
is marked with the Shared(*Yes)
. A shared member belongs to the class, not instances of a class. Shared members provide the shortcut of not needing to instance their owning class to use them--which is the primary payoff of a shared member.
VB.NET also has a shared
keyword which works exactly like AVR's Shared(*Yes)
. In C#, however, a shared member is marked with the static
keyword. Otherwise, though, the behavior a static C# class member is identical to an AVR for VB.NET shared class member.
Shared members rules-of-thumb
-
Don’t make
DclDB
,DclDiskFile
, orDclMemoryFile
objects shared. Although its legally syntactically, behavior of these IO-related objects when shared is quite hard to predict. - All public constants in a class are implicitly shared. Constants are, for all intents and purposes, the most pure functions ever. A constant never fiddles with program state and a constant always returns the same value.
- All shared members are shared by all users of Web applications. Ponder that statement a minute. It means that if you have a shared counter field in a Web app, that value is being shared by all users of the Web app! Probably not what you intended! Except for packaging library routines, be especially wary of shared members in browser-based applications.
- In any one class, shared members can only reference other shared members. Shared members cannot reference instance members from the same class.
-
Unlike C# and VB, AVR for .NET doesn't allow the
Shared(*Yes)
attribute at the class level; it is allowed in AVR only at the member level.
The most common mistake with shared members
A common mistake with shared members is to attempt to use them as a way to make class members globally available to other parts of the project.
In .NET, once a project has a reference to class, that class is available to all other classes in the project. Classes can be considered fully globally in your project. Even if a class is identified with a namespace other than what the project namespace is, that class is available anywhere in the project with a fully-qualified reference.
For example, consider putting a MyFuncs
class in a Utilities
namespace, as shown below in Figure 3.
DclNamespace Utilities
BegClass MyFuncs
...
EndClass
Figure 3. The MyFuncs
class inside the Utilities namespace
In any class in the project, the MyFuncs
class is available by either fully qualifying the class name or with a Using
statement, as shown below in Figure 4:
Using Utilities
BegClass Form1
// Getting access to the MyFuncs functions
// class via the `Using` statement.
DclFld mf1 Type(MyFuncs) New()
// Fully-qualified access to the MyFuncs class.
DclFld mf2 Type(Utilities.MyFuncs) New()
EndClass
Figure 4. Two ways
The point is that classes are global to a project and available anywhere in a project. However, class instances are not global. For example, in Figure 4 above, the class instances mf1
and mf2
are only available to the Form1
class instance. This leads us to this axiom:
Classes are available globally in a project; class instances are localized to the class in which they are declared.
What does this have to with shared variables? Let's say that you've declared an instance of MyFuncs
in Form1 as mf1
(as above in Figure 4) and now you need that instance available in Form2.
To achieve this you need to pass the mf1
reference to Form2.
This takes a little thought and care. You could, for example, customize Form2's
constructor and pass the mf1
reference to this constructor or you could pass the mf1
instance as an argument to a function.
Some AVR coders, especially newer ones, get a little waylaid by this need to pass class instance references around and just give up and make the members in MyFuncs
shared. This is, at least first, great because shared members are easily available because the class (not the class instance) is easily available. This solution usually breaks down quickly though because in Form2
what you really need is access to instance data (ie, non-shared members) from the mf1
instance--and shared members can only reference other shared members--they cannot reference instance members.
This leads us to another axiom:
Don't attempt to use shared members to break what are otherwise OOP's rigid rules of scope. Rather, engineer good ways to pass the class instances around as needed.
Ideal candidates for shared members
The generally qualifications for using a shared member in your code is pretty short:
- Constants (which is provided automatically for you)
- Pure functions (like date or time functions). You'll notice that all of the methods in the .NET System.Math class are shared (or static)--because they are all pure functions.
Beyond these qualifications, there are others but they exceed the scope of this article. One example of this is the Factory pattern.
Which brings us to our third axiom:
If you can’t state clearly why a member should be shared, you’re probably sharing it for the wrong reasons. (This axiom is often couched under the larger programming axiom: "If you don't know what you're doing, don't do it!")
Summary
Use shared members where they fit and make sense--but don't use them to get out from under a scoping issue. Making a member shared may indeed be a quick band aid to make a member visible to other classes, but more times than not, that quick-fix mentality comes back to bite you!
Remember also that shared variables in a Web app can be the path to pain and heartache. I helped rewrite an AVR ASP.NET app once where a frustrated developer gave up and made every member of a class shared. Every user of that Web app was working against the same shared data. There wasn't any correct data in sight!