.NET Framework 1.1 Performance Guidelines - Class Design Considerations

From Guidance Share

Jump to: navigation, search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, Rico Mariani, and Alex Mackman


Contents

Do Not Make Classes Thread Safe by Default

Consider carefully whether you need to make an individual class thread safe. Thread safety and synchronization is often required at a higher layer in the software architecture and not at an individual class level. When you design a specific class, you often do not know the proper level of atomicity, especially for lower-level classes.

For example, consider a thread safe collection class. The moment the class needs to be atomically updated with something else, such as another class or a count variable, the built-in thread safety is useless. Thread control is needed at a higher level. There are two problems in this situation. Firstly, the overhead from the thread-safety features that the class offers remains even though you do not require those features. Secondly, the collection class likely had a more complex design in the first place to offer those thread-safety services, which is a price you have to pay whenever you use the class.

In contrast to regular classes, static classes (those with only static methods) should be thread safe by default. Static classes have only global state, and generally offer services to initialize and manage that shared state for a whole process. This requires proper thread safety.


Consider Using the sealed Keyword

You can use the sealed keyword at the class and method level. In Visual Basic .NET, you can use the NotInheritable keyword at the class level or NotOverridable at the method level. If you do not want anybody to extend your base classes, you should mark them with the sealed keyword. Before you use the sealed keyword at the class level, you should carefully evaluate your extensibility requirements.

If you derive from a base class that has virtual members and you do not want anybody to extend the functionality of the derived class, you can consider sealing the virtual members in the derived class. Sealing the virtual methods makes them candidates for inlining and other compiler optimizations.

Consider the following example.

  public class MyClass{ 
    protected virtual void SomeMethod() { ... } 
  }

You can override and seal the method in a derived class.


  public class DerivedClass : MyClass { 
    protected override sealed void SomeMethod () { ... } 
  }


This code ends the chain of virtual overrides and makes DerivedClass.SomeMethod a candidate for inlining.


References

For more information about inheritance in Visual Basic .NET, see MSDNĀ® Magazine article, "Using Inheritance in the .NET World, Part 2," by Ted Pattison at http://msdn.microsoft.com/msdnmag/issues/01/12/instincts/.


Consider the Tradeoffs of Virtual Members

Use virtual members to provide extensibility. If you do not need to extend your class design, avoid virtual members because they are more expensive to call due to a virtual table lookup and they defeat certain run-time performance optimizations. For example, virtual members cannot be inlined by the compiler. Additionally, when you allow subtyping, you actually present a very complex contract to consumers and you inevitably end up with versioning problems when you attempt to upgrade your class in the future.


Consider Using Overloaded Methods

Consider having overloaded methods for varying parameters instead of having a sensitive method that takes a variable number of parameters. Such a method results in special code paths for each possible combination of parameters.


  //method taking variable number of arguments
  void GetCustomers (params object [] filterCriteria)
  //overloaded methods
  void GetCustomers (int countryId, int regionId)
  void GetCustomers (int countryId, int regionId, int CustomerType)


Note If there are COM clients accessing .NET components, using overloaded methods will not work as a strategy. Use methods with different names instead.


Consider Overriding the Equals Method for Value Types

You can override the Equals method for value types to improve performance of the Equals method. The Equals method is provided by System.Object. To use the standard implementation of Equals, your value type must be boxed and passed as an instance of the reference type System.ValueType. The Equals method then uses reflection to perform the comparison. However, the overhead associated with the conversions and reflections can easily be greater than the cost of the actual comparison that needs to be performed. As a result, an Equals method that is specific to your value type can do the required comparison significantly more cheaply.

The following code fragment shows an overridden Equals method implementation that improves performance by avoiding reflection costs.


  public struct Rectangle{
    public double Length;
    public double Breadth;
    public override bool Equals (object ob) {
    if(ob is Rectangle)
      return Equals((Rectangle)ob);
    else
      return false;
    }
    private bool Equals(Rectangle rect) {
      return this.Length == rect.Length && this.Breadth==rect.Breadth;
    }
  }


Know the Cost of Accessing a Property

A property looks like a field, but it is not, and it can have hidden costs. You can expose class-level member variables by using public fields or public properties. The use of properties represents good object-oriented programming practice because it allows you to encapsulate validation and security checks and to ensure that they are executed when the property is accessed, but their field-like appearance can cause them to be misused.

You need to be aware that if you access a property, additional code, such as validation logic, might be executed. This means that accessing a property might be slower than directly accessing a field. However, the additional code is generally there for good reason; for example, to ensure that only valid data is accepted.

For simple properties that contain no additional code (other than directly setting or getting a private member variable), there is no performance difference compared to accessing a public field because the compiler can inline the property code. However, things can easily become more complicated; for example, virtual properties cannot be inlined.

If your object is designed for remote access, you should use methods with multiple parameters instead of requiring the client to set multiple properties or fields. This reduces round trips.

It is extremely bad form to use properties to hide complex business rules or other costly operations, because there is a strong expectation by callers that properties are inexpensive. Design your classes accordingly.


Consider Private vs. Public Member Variables

In addition to the usual visibility concerns, you should also avoid unnecessary public members to prevent any additional serialization overhead when you use the XmlSerializer class, which serializes all public members by default.


Limit the Use of Volatile Fields

Limit the use of the volatile keyword because volatile fields restrict the way the compiler reads and writes the contents of the field. The compiler generates the code that always reads from the field's memory location instead of reading from a register that may have loaded the field's value. This means that accessing volatile fields is slower than nonvolatile ones because the system is forced to use memory addresses rather than registers.

Personal tools