This project is read-only.

Dotnet Commons Standards and Guidelines

1 Introduction

1.1 Summary

This document is intended for developers who are working on the Dotnet Commons project. The document itself will provide developers with a consistent standard framework for which developers will adhere to.

The document tries not to be too rigid on the standards to be used as not all developers are the same. By placing too many restrictions within the standards framework can be detrimental to a project in terms of both money and time.

The contents within this document will not be static in nature and therefore may change over time as the developers become more familiar with the project environment and structure.

This document requires or recommends certain practices developing programs in the C# language. The
objective of this coding standard is to have a positive effect on
  • Avoidance of errors/bugs, especially the hard-to-find
  • Maintainability, by promoting some proven design
  • Maintainability, by requiring or recommending a certain
  • Performance, by dissuading wasteful practices.

1.2 Source of References

Many of the rules and recommendations were taken from the MSDN C# Usage Guidelines. The naming guidelines in that document are identical to those found in Appendix C of the ECMA C# Language Specification. Naming standards and other style issues are more or less arbitrary, so it seems prudent to follow an existing convention. The naming standard in this document differs from that in the given references only in some miniscule details that will hardly ever occur in practice.

1.3 References

Ref. doc. number Author Title

1 XJS-154-1215 Bart van Tongeren PMS-MR C++ Coding Standard
2 ECMA-334 TC39/TG2 C# Language Specification, ed. Dec 20012
3 msdn, unnumbered Design Guidelines for .NET Class Library Developers

2 General Rules

2.1 Rules and Recommendations

Rule 1. Every time a recommendation is not followed, this must have a good reason
Good reasons do not include personal preferences of style.

Rule 2. Do not mix code from different sources in 1 source file
In general, third party code will not comply with the coding standard, so do not put such code in the same file as proprietary code modifying to the third party code to conform to the coding standards and naming convention.

3 Naming Standards

3.1 Naming Conventions

Naming conventions to be most closely follow those naming standards put out by Microsoft. This standards can be found at the following this link: Naming Guidelines.

3.2 Capitalization Styles

The following section describes different ways of capitalizing identifiers.

Pascal casing
This convention capitalizes the first character of each word. For example:
Color BitConverter

Camel casing
This convention capitalizes the first character of each word except the first word. For example:
backgroundColor totalValueCount

All uppercase
Only use all uppercase letters for an identifier if it contains an abbreviation. For example:

Exception to the Naming Convention

3.3 Capitalization Summary

Type Standard Examples
Namespaces Pascal case, no underscores. Use Dotnet.Commons as root. Note that any acronyms of 3 characters or more should be Pascal case (System.Xml instead of System.XML) instead of all caps. Acronyms under 3 characters should be all caps (System.IO). Dotnet.Commons.DataAdapters, Dotnet.Commons.Lang
Assemblies If the assembly contains a single name space, or has an entire self-contained root namespace, name the assembly the same name as the namespace. Dotnet.Commons.Collections.dll, Dotnet.Commons.Lang.dll
Classes and Structs Pascal Case, no underscores or leading "C" or "cls". Classes may begin with an "I" only if the letter following the I is not capitalised, otherwise it looks like an Interface. Classes should not have the same name as the namespace in which they reside. Any acronyms of three or more letters should be pascal case, not all caps. Try to avoid abbreviations, and try to always use nouns. Classes / Struts must differ by more than case to be usable from case-insensitive languages like Visual Basic .Net. Widget, InstanceManager, XmlDocument
Collection Classes Follow class naming conventions, but add Collection to the end of the name WidgetCollection
Delegate Classes Follow class naming conventions, but add Delegate to the end of the name WidgetCallbackDelegate
Exception Classes Follow class naming conventions, but add Exception to the end of the name InvalidTransactionException
Attribute Classes Follow class naming conventions, but add Attribute to the end of the name WebServiceAttribute
Interfaces Follow class naming conventions, but start the name with "I" and capitalize the letter following the I IWidget
Enumerations Follow class naming conventions. Do not add "Enum" to the end of the enumeration name. If the enumeration represents a set of bitwise flags, end the name with a plural. SearchOptions
Public and Protected Methods Pascal Case, no underscores except in the event handlers. Try to avoid abbreviations. Methods must differ by more than case to be usable from case-insensitive languages like Visual Basic .Net public void DoSomething(...)
Private Methods Camel Case, no underscores except in the event handlers. Try to avoid abbreviations. Methods must differ by more than case to be usable from case-insensitive languages like Visual Basic .Net public void doSomethingPrivately(...)
Properties Pascal Case, no underscores. Try to avoid abbreviations. Properties must differ by more than case to be usable from case-insensitive languages like Visual Basic .Net. public int RecordID
Parameters Camel Case. Try to avoid abbreviations. Parameters must differ by more than case to be usable from case-insensitive languages like Visual Basic .Net. ref int recordID
Method-Level Variables Camel Case, NO Hungarian Notation recorded, iCustomer, bIsValid
Class-Level Private and Protected variables (Fields) Camel Case with Leading Underscore. private int _recordID;
Controls on Forms Modified 3 to 4 letter Hungarian Notation (where possible) prefixes using .Net Class Names txtUserID, lblHeader, lstChoices, btnSubmit
Constants ALL upper case, words separated with the under score character. Note: Public static readonly fields WILL BE treated as constant as thus the name of such a field MUST be in CAPITALS. private const string CONFIG_FILE = “conf.xml”; private const int MAX_VALUE = 10; public static readonly string SOME_READONLY_FIELD = “someConstant”.ToString();
Objects Camel Cases, object type suffix where possible. IDictionary productDict = new Hashtable()customerDict,accountList,configXmlDoc

Word choice
  • Do avoid using class names duplicated in heavily used namespaces. For example, don’t use the following for a class name.
System Collections Forms UI
  • Do not use abbreviations in identifiers.
  • If you must use abbreviations, do use camelCase for any abbreviation containing more than two characters, even if this is not the usual abbreviation.
The general rule for namespace naming is: CompanyName.TechnologyName.
  • Do avoid the possibility of two published namespaces having the same name, by prefixing namespace names with a company name or other well-established brand. For example, Microsoft.Office for the Office Automation classes provided by Microsoft.
  • Do use PascalCase, and separate logical components with periods (as in Microsoft.Office.PowerPoint). If your brand employs non-traditional casing, do follow the casing defined by your brand, even if it deviates from normal namespace casing (for example, NeXT.WebObjects, and ee.cummings).
  • Do use plural namespace names where appropriate. For example, use System.Collections rather than System.Collection. Exceptions to this rule are brand names and abbreviations. For example, use System.IO not System.IOs.
  • Do not have namespaces and classes with the same name.
  • Do name classes with nouns or noun phrases.
  • Do use PascalCase.
  • Do use sparingly, abbreviations in class names.
  • Do not use any prefix (such as “C”, for example). Where possible, avoid starting with the letter “I”, since that is the recommended prefix for interface names. If you must start with that letter, make sure the second character is lowercase, as in IdentityStore.
  • Do not use any underscores.
public class FileStream { … }
public class Button { … }
public class String { … }
  • Do name interfaces with nouns or noun phrases, or adjectives describing behavior. For example, IComponent (descriptive noun), ICustomAttributeProvider (noun phrase), and IPersistable (adjective).
  • Do use PascalCase.
  • Do use sparingly, abbreviations in interface names.
  • Do not use any underscores.
  • Do prefix interface names with the letter “I”, to indicate that the type is an interface.
  • Do use similar names when defining a class/interface pair where the class is a standard implementation of the interface. The names should differ only by the “I” prefix in the interface name. This approach is used for the interface IComponent and its standard implementation, Component.
public interface IComponent { … }
public class Component : IComponent { … }
public interface IServiceProvider{ … }
public interface IFormatable { … }
  • Do use PascalCase for enums.
  • Do use PascalCase for enum value names.
  • Do use sparingly, abbreviations in enum names.
  • Do not use a family-name prefix on enum.
  • Do not use any “Enum” suffix on enum types.
  • Do use a singular name for enums
  • Do use a plural name for bit fields
  • Do define enumerated values using an enum if they are used in a parameter or property. This gives development tools a chance at knowing the possible values for a property or parameter.
public enum FileMode{
  • Do use the Flags custom attribute if the numeric values are meant to be bitwise ORed together
public enum Bindings {
  • Do use int as the underlying type of an enum. (An exception to this rule is if the enum represents flags and there are more than 32 flags, or the enum may grow to that many flags in the future, or the type needs to be different from int for backward compatibility.)
  • Do use enums only if the value can be completely expressed as a set of bit flags. Do not use enums for open sets (such as operating system version).
Static fields
  • Do name static members with nouns, noun phrases, or abbreviations for nouns.
  • Do name static members using PascalCase.
  • Do not use Hungarian-type prefixes on static member names.
  • Do use descriptive names such that a parameter’s name and type clearly imply its meaning.
  • Do name parameters using camelCase.
  • Do prefer names based on a parameter’s meaning, to names based on the parameter’s type. It is likely that development tools will provide the information about type in a convenient way, so the parameter name can be put to better use describing semantics rather than type.
  • Do not reserve parameters for future use. If more data is need in the next version, a new overload can be added.
  • Do not use Hungarian-type prefixes.
Type GetType (string typeName)
string Format (string format, object [] args)
  • Do name methods with verbs or verb phrases.
  • Do name methods with Pascal Case
RemoveAll(), GetCharArray(), Invoke()
  • Do name properties using noun or noun phrases
  • Do name properties with Pascal Case
  • Consider having a property with the same as a type. When declaring a property with the same name as a type, also make the type of the property be that type. In other words, the following is okay
public enum Color {...}
public class Control {
    public Color Color { get {...} set {...} }

but this is not
public enum Color {...}
public class Control {
    public int Color { get {...} set {...} }

In the latter case, it will not be possible to refer to the members of the Color enum because Color.Xxx will be interpreted as being a member access that first gets the value of the Color property (of type int) and then accesses a member of that value (which would have to be an instance member of System.Int32).
  • Do name event handlers with the “EventHandler” suffix.
public delegate void MouseEventHandler(object sender, MouseEvent e);
  • Do use two parameters named sender and e. The sender parameter represents the object that raised the event, and this parameter is always of type object, even if it is possible to employ a more specific type. The state associated with the event is encapsulated in an instance e of an event class. Use an appropriate and specific event class for its type.
public delegate void MouseEventHandler(object sender, MouseEvent e);
  • Do name event argument classes with the “EventArgs” suffix.
public class MouseEventArgs : EventArgs {
    int x;
    int y;
    public MouseEventArgs(int x, int y) 
       { this.x = x; this.y = y; }
    public int X { get { return x; } } 
    public int Y { get { return y; } } 
  • Do name event names that have a concept of pre- and post-operation using the present and past tense (do not use BeforeXxx/AfterXxx pattern). For example, a close event that could be canceled would have a Closing and Closed event.
public event ControlEventHandler ControlAdded {
  • Consider naming events with a verb.
Case sensitivity
  • Don’t use names that require case sensitivity. Components might need to be usable from both case-sensitive and case-insensitive languages. Since case-insensitive languages cannot distinguish between two names within the same context that differ only by case, components must avoid this situation.
Examples of what not to do:
  • Don’t have two namespaces whose names differ only by case.
namespace ee.cummings;
namespace Ee.Cummings;
  • Don’t have a method with two parameters whose names differ only by case.
void F(string a, string A)
  • Don’t have a namespace with two types whose names differ only by case.
System.WinForms.Point p;
System.WinForms.POINT pp;
  • Don’t have a type with two properties whose names differ only by case.
int F {get, set};
int F {get, set}
  • Don’t have a type with two methods whose names differ only by case.
void f();
void F();
Avoiding type name confusion
Different languages use different names to identify the fundamental managed types, so in a multi-language environment, designers must take care to avoid language-specific terminology. This section describes a set of rules that help avoid type name confusion.
  • Do use semantically interesting names rather than type names.
  • In the rare case that a parameter has no semantic meaning beyond its type, use a generic name. This is commonly seen in Java code.

For example, a class that supports writing a variety of data types into a stream might have:
void Write(double value);
void Write(float value);
void Write(long value);
void Write(int value);
void Write(short value);

rather than a language-specific alternative such as:
void Write(double doubleValue);
void Write(float floatValue);
void Write(long longValue);
void Write(int intValue);
void Write(short shortValue);
  • In the extremely rare case that it is necessary to have a uniquely named method for each fundamental data type, do use the following universal type names: Sbyte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Boolean, Char, String, and Object. For example, a class that supports reading a variety of data types from a stream might have:
double ReadDouble();
float ReadSingle();
long ReadIn64();
int ReadInt32();
short ReadInt16();

rather than a language-specific alternative such as:
double ReadDouble();
float ReadFloat();
long ReadLong();
int ReadInt();
short ReadShort();

Folder and Filename Conventions

Folder Structure Conventions

All projects should be contained under the “Dotnet.Commons\” folder.
Under this main default project folder structure, a folder should be created for every namespace. This makes it easier to map namespaces to the folder layout.
Therefore if you have a class in the namespace Dotnet.Commons.Collections, you would see the class under the folder structure as follows:

Filename Conventions

Each Class within a project is to be place in its own physical file with the same filename as the class name. Therefore if you have two classes called “MyClass” and “YourClass” then “MyClass” should be stored physically in the file MyClass.cs and “YourClass” should be physically stored in the file YourClass.cs. You should never have multiple classes in the same physical file.

Custom Exceptions

Custom Exceptions should be inherited from a System.Exception class (not ApplicationException).

4 Coding Standards

General Coding Standards


Comments used within the project will be compliant with the “MSDN C# Programmer's Reference - Recommended Tags for Documentation Comments” Document ( and will be extracted via the open source application - NDoc Code Documentation Generator for .NET (
The /doc compiler option should be turned on for all projects, as this forces comments to be added to all public classes, methods and properties. The /doc option also produces an XML representation of the comments which can be used to produce a technical specification document using NDoc.
To turn on the /doc compiler option within the Visual Studio .Net environment, you must enter a “XML Document File” name as shown below:

While not absolutely necessary, it is best to name the XML documentation file with the same base name as the name of your assembly (but with the xml extension). Class Level
All classes should contain a summary and remarks comment tags and must provide the following minimum information
/// <summary>[DESCRIPTION]</summary>

Example of usage is as follows:
/// <summary>Holds details relating to Car.</summary>
public class Car
	. . . 
} Method Level
All public methods should contain a summary comment tag.
/// <summary>[DESCRIPTION]</summary>

The following methods also require the following:
  • If a method contains any parameters then a param tag should be used.
/// <param name="[PARAM NAME]">[DESCRIPTION]</param>

/// <summary>This method does what I want it to</summary>
/// <param name="Int1">Used to indicate status.</param>
public static void MyMethod(int Int1)

  • If a method returns a value then the returns tag should be used.
/// <returns>[DESCRIPTION]</returns>
/// <summary>This method gets a zero</summary>
/// <returns>Returns zero.</returns>
public int GetZero()
    return 0;

  • If a method is overloaded then an overloads tag should be included on the first overloaded method.
/// <overloads>[OVERLOAD DESCRIPTION]overloads>
/// <summary>This overload just says hello.</summary>
/// <overloads>This method has two overloads.</overloads>
public void SayHello()

/// <summary>This one says hello to someone.</summary>
public void SayHello(string toSomeone)

  • If a method throws an exception then the exception tag should be used.
/// <exception cref="[EXCEPTION]">[THROW TYPE/REASON]</exception>
/// <summary>This method services the some.</summary>
/// <exception cref="System.Exception">Thrown when...</exception>
public void SomeMethod()
} Property Level
All public properties should contain a summary and value comment tags.

/// <summary>DESCRIPTION</summary>
/// <value>VALUE DESCRIPTION</value>

/// <summary>MyProperty is a property in the Car class.</summary>
/// <value>A string containing the text "MyProperty String".</value>
public string MyProperty
      	return "MyProperty String";

4.1.2 Inline Comments

Include inline comments throughout your code as is per normal programming practices.
For the following comments, please follow this structure:
  • Change Comments


// CHANGE – JB – 27/10/2004 – Changed increment value from 1 to 4

Note: Please update class history (as shown in class level comments) for summary description of the changes carried out.

Note: The word CHANGE must be in UPPER CASE
  • ToDo Comments


// TODO – DB – JL – 27/10/2004 - Change code to add 10

This is asking DB to do the changes
// TODO –– JL – Change Steering method for stick to wheel

This TODO is not directed to any particular developer

Note: The word TODO must be in UPPER CASE.

4.2 Regions

Regions should be used throughout the project to break up code into related groups.

Your classes should have the at least some of the following region types (if applicable):
  • Constructors - #region Constructors
  • Finalizer - #region Finalizer
  • Structure Types - #region Structs
  • Enum Types - #region Enums
  • Delegates - #region Delegates
  • Fields (Private Class Members) - #region Fields
  • Constants (Class Constants Only. Not Method Level Constants) - #region Constants
  • Properties - #region Properties
  • Methods - #region Methods

Where classes are complex and large, it is recommended to have regions based on accessibility levels (Public, Private Protected, Internal, and Protected Internal) as shown in the following:
  • Public [Region Type] - #region Public [Region Type]
  • Private [Region Type] - #region Private [Region Type]
  • Protected [Region Type] - #region Protected [Region Type]
  • Internal [Region Type] - #region Internal [Region Type]
  • and Protected Internal [Region Type] - #region Protected Internal [Region Type]

An example of regions can be seen in the usage of the following Method grouping:

Simple Classes:
        #region Methods
        // All Methods Here
        #endregion  // End Methods Region

	Large / Complex Classes:

        #region Public Methods 
        // Public Methods Here
        #endregion  // End Public Methods Region

        #region Private Methods
        // Public Methods Here
        #endregion  // End Private Methods Region

5 Object Oriented Programming Standards

5.1 OOP Rules and Recommendations

Rule 3. Declare all fields (data members) private.
Exceptions to this rule are static readonly fields, which may have any accessibility deemed appropriate.

Rule 4. Provide a default private constructor if there are only static methods and properties on a class.
Instantiating such a class would be useless.

Rule 5. Explicitly define a protected constructor on an abstract base class.
Of course an abstract class cannot be instantiated, so a public constructor should be harmless. However, 3
Many compilers will insert a public or protected constructor if you do not. Therefore,
for better documentation and readability of your source code, you should explicitly define a
protected constructor on all abstract classes.
Dubious reasoning, but harmless. This recommendation is provisional.

Rule 6. Selection statements (if-else and switch) should be used when the control flow depends on an object’s value; dynamic binding should be used when the control flow depends on the object’s type.
This is a general OO principle. Please note that it is usually a design error to write a selection statement that queries the type of an object (keywords typeof, is).

Using a selection statement to determine if some object implements one or more optional interfaces is a valid construct though.

Rule 7. All variants of an overloaded method shall be used for the same purpose and have similar behavior.
Doing otherwise is against the Principle of Least Surprise.

Rule 8. If you must provide the ability to override a method, make only the most complete overload virtual and define the other operations in terms of it.
Using the pattern illustrated below requires a derived class to only override the virtual method. Since all the other methods are implemented by calling the most complete overload, they will automatically use the new implementation provided by the derived class.

An even better approach, not required by this coding standard, is to refrain from making virtual methods
public, but to give them protected5 accessibility, changing the sample above into:

Rule 9. Specify methods using preconditions, postconditions, exceptions; specify classes using invariants.
You can use Debug.Assert to ensure that pre- and post-conditions are only checked in debug builds. In release builds, this method does not result in any code.
  • Rule 10. Use C# to describe preconditions, postconditions, exceptions, and class invariants.*
Compilable preconditions etc. are testable.
The exact form (e.g. assertions, special DbC functions such as require and ensure) is not discussed here.
However, a non-testable (text only) precondition is better than a missing one.
  • Rule 11. Do not overload any ‘modifying’ operators on a class type.*
In this context the ‘modifying’ operators are those that have a corresponding assignment operator, i.e. the nonunary versions of +, -, *, /, %, &, |, ^, << and >>.
There is very little literature regarding operator overloading in C#. Therefore it is wise to approach this feature with some caution.

Overloading operators on a struct type is good practice, since it is a value type. The class is a reference type and users will probably expect reference semantics, which are not provided by most operators.

Consider a class Foo with an overloaded operator(int), and thus an impicitly overloaded operator=(int). If we define the function AddTwenty as follows:
public static void AddTwenty (Foo f)
f += 20;

Then this function has no net effect:
	Foo bar = new Foo(5);
	AddTwenty (bar);
	// note that ‘bar’ is unchanged
	// the Foo object with value 25 is on its way to the GC...

The exception to this recommendation is a class type that has complete value semantics, like

Rule 12. Do not modify the value of any of the operands in the implementation of an overloaded operator.
This rule can be found in a non-normative clause of 2, section 17.9.1. Breaking this rule gives counterintuitive results.

Rule 13. If you implement one of operator==(), the Equals method or GetHashCode(), implement all three.
Also override this trio when you implement the IComparable interface.

Do consider implementing all relational operators (!=, <, <=, >, >=) if you implement any.

If your Equals method can throw, this may cause problems if objects of that type are put into a container. Do consider to return false for a null argument.

The MSDN guidelines 3 recommend to return false rather than throwing an exception when two incomparable objects, say the proverbial apples and oranges, are compared. Since this approach sacrifices the last remnants of type-safety, this recommendation has been weakened.

Rule 14. Use a struct when value semantics are desired.
More precisely, a struct should be considered for types that meet any of the following criteria:
  • Act like primitive types.
  • Have an instance size under 16 bytes.
  • Are immutable.
  • Value semantics are desirable.
Remember that a struct cannot be derived from.

Rule 15. Allow properties to be set in any order.
Properties should be stateless with respect to other properties, i.e. there should not be an observable difference between first setting property A and then B and its reverse.

Rule 16. Use the property accessors rather than accessing private member fields directly.
Instead of doing the following:
Public class MyClass
	private string _myfield;
	public void foobar()
		this._myfield = “Hello”;
		Console.Writeln(“My field is {0}”, this._myField);

One should do the following instead:
Public class MyClass
	private string _myfield;

	public string MyField
		get { return this._myfield; }
		set { this._myfield = value; }

	public void foobar()
		this.Myfield = “Hello”;
		Console.Writeln(“My field is {0}”, this.MyField);

The reason being that it is possible to have additional statements inside the property get and set method doing something before returning or setting the field value. Consider the following example:
Public class MyClass
	private string _myfield;

	public string MyField
		get { return this._myfield.ToLower(); }
		set { this._myfield = value + “ world”; }

	public void foobar()
		this.Myfield = “Hello”;
		Console.Writeln(“My field is {0}”, this.MyField);

Rule 17. Use a property rather than a method when the member is a logical data member.

Rule 18. Use a method rather than a property when this is more appropriate.
In some cases a method is better than a property:
  • The operation is a conversion, such as Object.ToString.
  • The operation is expensive enough that you want to communicate to the user that they should consider caching the result.
  • Obtaining a property value using the get accessor would have an observable side effect.
  • Calling the member twice in succession produces different results.
  • The order of execution is important.
  • The member is static but returns a value that can be changed.
  • The member returns a copy of an internal array or other reference type.
  • Only a set accessor would be supplied. Write-only properties tend to be confusing.

Rule 19. Do not create a constructor that does not yield a fully initialized object.
Only create constructors that construct objects that are fully initialized. There shall be no need to set additional properties.

Rule 20. Always check the result of an as operation.
If you use as to obtain a certain interface reference from an object, always ensure that this operation does not return null. Failure to do so may cause a NullReferenceException at a later stage if the object did not implement that interface.

Rule 21. Use explicit interface implementation only to prevent name-clashing or to support optional interfaces.
When you use explicit interface implementation, then the methods implemented by the class involved will not be visible through the class interface. To access those methods, you must first cast the class object to the requested interface.
It is recommended to use explicit interface implementation only:
  • When you want to prevent name clashing. This can happen when multiple interfaces must be supported which have equally named methods, or when an existing class must support a new interface in which the interface has a member which name clashes with a member of the class.
  • When you want to support several optional interfaces (e.g. IEnumerator, IComparer, etc) and you do not want to clutter your class interface with their members.
Consider the following example.

public interface IFoo1
		void Foo()

public interface IFoo2
	void Foo()

public class FooClass : IFoo1, IFoo2
	// This Foo is only accessible by explictly casting to IFoo1

	void IFoo1.Foo() { … }

	// This Foo is only accessible by explictly casting to IFoo2
	void IFoo2.Foo() { … )

6 Building and Releasing Standards

6.1 Checking for .Net Compliance

FxCop ( is a code analysis tool that checks .NET managed code assemblies for conformance to the Microsoft .NET Framework Design Guidelines.

The tool checks for more than 200 defects in the following areas:
  • Library design
  • Localization
  • Naming conventions
  • Performance
  • Security

This tool should be run against any .net code before its release.

Last edited Mar 9, 2007 at 8:23 AM by JoeBlack, version 6


No comments yet.