Released on: 3/16/2005 | updated on: 3/16/2005
K. Scott Allen
This article discusses:
• |
Performance and behavior of Type constructor |
• |
Static member and thread security |
• |
Static classes in. NET Framework 2.0 |
• |
Best practices for some type of shared members |
|
This article uses the following technologies: . NET Framework, C #, Visual Basic The download code is located: StaticsinNET.exe(150KB) |
Content on this page
|
Exact price |
|
Out-of-instance rules |
|
Create a function lock |
|
Static reflection |
|
Static classes in. NET Framework 2.0 |
|
Static local variables |
|
Static illusion |
|
Static Summary |
When you operate in A. NET-based application, you often encounter types with Shared methods or static fields. Because these abstractions have special behaviors, you need to ask important questions about implementation. When will the runtime initialize static Fields? Is the thread of this method secure? Does this class cause bottlenecks?
In this article, I will introduce you to the seven features of static content. This involves some details about the static constructor and how the C # and Visual Basic compilers work with the Runtime Library to implement additional security in the background. At the end of this article, you will also see best practices for using static members and static classes in applications.
Precise cost
The Type constructor is also called the type Initial Value Setting item. They can initialize the status of a static or shared field to a predefined value or calculated value. In either case, you can obtain the type initial value. The first case is that the developer uses the Shared keyword on the New subroutine of Visual Basic, or adds a static, non-parameter method with the same name as the type in C, to explicitly add a Type constructor. The second case is that a type has an initial value set for a static field. In this case, the compiler adds a Type constructor to the back.Figure 1The two cases of Visual Basic. NET are displayed.
If you compileFigure 1Class, and then run the obtained assembly using the FxCop tool, it will find that it generates a severe warning about the ExplicitConstructor class. The warning message is "Do not declare explicit static constructors ". The description of this rule tells you that explicit static constructor will cause poor code performance. FxCop recommends initializing static fields. But we don't just use this recommendation on the surface, but we need to take a deeper look at the use of the disassembly program or the anti-compiler assembly (such as ILDASM), in order to truly understand the causes of performance differences.
In Microsoft intermediate language (MSIL), the Type constructor name is. cctor (abbreviation of class constructor ). The. cctor method of ExplicitConstructor and ImplicitConstructor is as follows:Figure 2.
Figure 2MSIL in does not provide any prompts about performance differences. The compiler provides almost identical code for implicit type constructors in ImplicitConstructor and explicit type constructors in ExplicitConstructor. If you have a better understanding of ILDASM, you will see the difference between the metadata of the two classes:
.class private auto ansi ExplicitConstructor
extends [mscorlib]System.Object
{
} // end of class ExplicitConstructor
.class private auto ansi beforefieldinit ImplicitConstructor
extends [mscorlib]System.Object
{
} // end of class ImplicitConstructor
Note that ImplicitConstructor has an additional metadata identifier named beforefieldinit. This flag enables the Runtime Library to execute the Type constructor method at any time, as long as the method is executed before the first access to the type of static field. In other words, beforefieldinit provides the Runtime library with a license for Performing proactive optimization. Without beforefieldinit, The Runtime Library must run the Type constructor at a specific time, that is, just before the first access to the type of static or instance fields and methods. When an explicit Type constructor exists, the compiler does not use beforefieldinit to mark this type. precise timing restrictions may cause performance degradation implied by FxCop. This rule exists in both C # and Visual Basic. You can useFigure 3To enlarge this performance difference.
You don't need a high-precision timer to see the speed difference. On my 2.8 GHz Pentium 4, the execution time of the first loop (using ExplicitConstructor) is approximately eight times that of the second loop (using ImplicitConstructor. The check executed by the Runtime Library to set the initial value of the running type at the exact time increases the overhead in the loop, while beforefieldinit relaxed the limitations of these rules, and allows the Runtime Library to perform these checks outside the loop.
Figure 3The code in is the worst case. You need to weigh when using an explicit Type constructor. Few classes really need class constructors to execute at a precise time, so in most cases, it makes sense to allow the compiler to add the beforefieldinit flag and avoid explicit type constructors.
Back to Top
Exception rules
Another unique behavior of a Type constructor is to run an exception in a library management constructor. When an exception is thrown, the runtime starts to look for its filter to specify the catch clause closest to the exception. NowFigure 4Take the C # Console mode program in as an example.
This program will generateFigure 5. There are two actions that need attention. First, the program will not look for catch clauses for exceptions of the type ApplicationException, even if such exceptions are caused by static constructors. The runtime Stops any exceptions that attempt to retain the Type constructor and packs the exception into a new TypeInitializationException object. Then, the original exception thrown by this type of constructor is located in the InnerException attribute of the TypeInitializationException object.
One of the possible causes of this special behavior is that the runtime no longer tries to call this type of constructor when the second attempt to access this type of static property, this causes the same exception observed in the first iteration. The Runtime Library does not give the Type constructor a second opportunity. If the exception is caused by an initial value of a static field, the same rule exists. As you can see earlier, the code for the static field Initial Value Setting item runs within the implicit Type constructor.
TypeInitializationException may be a fatal error in the application because it will generate useless types. If it is possible to recover from an error, you should plan to capture any exceptions in the Type constructor. If the error cannot be reconciled, you should allow the application to terminate.
Back to Top
Constructor lock
The Type constructor has a strange behavior. This behavior is caused by the Common Language architecture (CLI) specification. This specification ensures that a Type constructor is executed only once unless the user code is explicitly called. To enforce this guarantee in a multi-threaded environment, a lock is required to synchronize the thread. The runtime thread must obtain this lock before calling the Type constructor.
The program that uses the lock must be very careful to avoid deadlock. To learn how it happened, try to useFigure 6Bad code puts the running database in a deadlock state. This code sends thread A to the Static1 Type constructor, sends thread B to the Static2 Type constructor, and puts both threads in sleep state. Thread A will wake up and need to access Static2, and B has locked it. Thread B will wake up and need to access Static1, and A has locked it. Thread A requires the lock held by thread B, while thread B requires the lock held by thread. This is a typical deadlock.
Figure 6The application in will not cause deadlocks, but will generate the results shown in figure 7. It proves that the CLI specification also ensures that the Runtime Library does not allow type constructors to generate deadlocks, unless the User Code explicitly uses an attached lock.
Figure 7CLI and deadlock prevention
As shown in figure 7, the Runtime Library avoids the deadlock by allowing Static2 to access the static Message attribute of Static1 before the Static1 Type constructor completes execution. The message in Static1 should be "Hello From Static1", but the message you see is "blank ". This program also demonstrates how to execute the static field Initial Value Setting item before the code in the explicit Type constructor.
You may realize that this problem is similar to the failure of static initialization in C ++. The experience here is to avoid hitting another type of static member in the Type constructor. Although there are few opportunities to see the previous situation, it is difficult to debug and track the results because there are few available diagnoses.
Back to Top
Static reflection
The ability to check metadata of a type or object instance at runtime is a very powerful function. Reflection enables you to build tools such as object browsers, and also enables you to create scalable applications using the plug-in type feature at runtime. There is a slight difference between using anti-injection technology on static members of the type and reflection on instance members of the object. For example,Figure 8The code in will get the message Value of the static property from type static1. It also uses the type reference of static2 to try to obtain the same attribute value.
When the Code uses getproperties to extract static1 attributes, it passes the bound flag to indicate that it wants static members with public visibility. These flags are enough to obtain the metadata of the message attribute in the array of propertyinfo objects. Next, the getvalue method of the first propertyinfo object in the array will be called (here is the propertyinfo of the message attribute ). Generally, you need to pass the instance of the object to getvalue as the first parameter, but because it is a static member, no object can be passed. When using static attributes, you can use an empty reference of the Instance instead.
Finally, try to use the type object of static2 to obtain the message value. Static members will not be inherited, but the intelliisense and C # compilers will have the illusion of inheritance, which I will discuss later. In this case, to obtain the message attribute, you need an additional bind flag flattenhierarchy, which will tell the runtime to include the base type in the search of static members.
Back to Top
Static classes in. NET Framework 2.0
The class used previously has several design defects. Even if these classes do not have instance members, you can still use the C # or new operator in Visual Basic to create instances of these classes. In addition, even if you never intend to use these classes as the base classes, some classes will inherit from these classes.
The solution to the Inheritance Problem is to apply the sealed keyword in C # Or the notinheritable keyword in Visual Basic. To prevent code outside the class from creating these classes (although it will still be instantiated by the class members), you can add private Default constructors to these classes. When C #2.0 is used, the solution to these two problems is to apply the static keyword at the class level, as shown inFigure 9.
This new syntax enables the compiler to force some new rules. You cannot declare a static1 variable because it is marked as a static class. You cannot derive from static1 or add any non-static members to the class. Note that it is wrong to derive a static class from any non-object class.
When you upgrade to. NET Framework 2.0, you should review your C # class to ensure that there are no instance methods. These classes will be alternative to using the static keyword in the class declaration. The additional compilation time check described earlier in this article will make this review worthwhile. In the current beta version, Visual Basic does not support shared keywords in class declarations. However, Visual Basic still has some C # unavailability skills.
Back to Top
Static local variables
A little-known function in Visual Basic is to support static variables in sub or function. Static local variables retain their values between method calls. From this point of view, their behavior is like a class-level variable, but their visibility is limited to a single method.
Static local variables have been released in Visual Basic versions earlier than. net. Therefore, this function is probably used to simplify the porting of old-style code. Although C and C ++ programmers are very familiar with static local variables, C # has not developed this function. Static local variables are very interesting because the Common Language Runtime Library (CLR) does not support static variables in methods. No matter what,Figure 10The code in will be compiled and executed, and the expected results will be generated by outputting "Count = 1" to "Count = 10.
Because CLR does not support static local variables, the Visual Basic compiler must execute some additional work to make the program succeed. The compiler adds two variables to the class using two special names, as shown in figureFigure 11As seen in ILDASM. The first field in this example is an integer field named $ STATIC $ GetMessage $ 200E $ I. This field will retain static local variables in GetMessageI. The second field is of the StaticLocalInitFlag type. It assists in correctly initializing variables when performing the method for each instance of the Foo class for the first time.I. Note that these fields are not shared fields, and each instance of Foo has a new local variable to be initialized and used.I.
Some operations are required for static local variable volume initialization. If you search for the GetMessage method in MSIL, 62 commands are found (compared to 13 commands that use similar methods not only for class-level fields ). Most of these commands are for checkIAnd use the lock to execute thread-safe initialization.
Because the Runtime Library has overhead when using static local variables, you 'd better avoid using this function in the current design. If you have used static local variables to port code from Visual Basic 6.0 to Visual Basic.. NET, you must consider recreating the variable as a class-level variable. This task should be relatively simple.
Back to Top
Static illusion
Static local variables are not the only compiler skills. As mentioned earlier, CLR does not support inheritance of static members. In any case, both Visual Basic and C # compilers allow you to use derived type names to access static members of the base type. In addition, Visual Basic. NET allows you to access static members through instances of the type or even instances of the derived type, suchFigure 12As shown in the code.
ForFigure 12When studying msil in the main method, we found that the Visual Basic compiler will map two statements with the message attribute to a static1: get_message call, and completely enclose the static2 class. Even if you do not use the static2 instance, the compiler will still create this new object so that no side effects of calling this constructor will be missed.
Static members are not encouraged through instance variables. In fact, currently Visual Studio? In Visual Basic beta 2005, a compiler warning "access of shared member through an instance" is generated ". In C #, if you use static members of the class through instance variables, it will cause a compiler error. However, you can still use this technique through the Visual Basic compiler, A derived type class name is used to access static members.
Back to Top
Static Summary
You must weigh the pros and cons when adding static members to the type at design time and runtime. During design, you need to carefully select the members to mark as static and avoid losing the object-oriented design. Do not try to create a dump platform for unrelated static methods to combine different functions into a class. If the class only contains static members, remember to mark the class as static, or seal the class (notinheritable), and provide a private constructor for the class to avoid instance creation.
You should also remember that the static classes that are retained between method calls should be thread-safe by default. To ensure the thread security of a class, you must be very careful when implementing and testing the class. Before proceeding with this method, please ask yourself if this additional overhead is required.
Line security also has performance issues during running. In this article, you have seen the performance results when the class has a static constructor. When writing code for reusable libraries, it is especially important to evaluate performance implication. You cannot know when people will use your class in the worst case, as described at the beginning of this article.
Finally, please make a good record for your static class. You can use static methods (such as Open and Copy in System. IO. FileClass) to perform common operations. Once you implement an important class that maintains state between method calls, users of this class will want to learn more about thread security and performance. The more you describe the class, the less time you will spend in troubleshooting the problem later.
K. Scott AllenIs the chief software architect of Medisolv Inc., based in Columbia, MD. Scott is also the founder of the. NET community website OdeToCode.com. You can useScott@OdeToCode.comContact him and read his network diary.
Go to the original English page
Back to Top