Type Design Guidelines in .NET
From the CLR perspective, there are only two categories of types—reference types and value types—but for the purpose of framework design discussion we divide types into more logical groups, each with its own specific design rules. Figure 4-1 shows these logical groups.
Classes are the general case of reference types. They make up the bulk of types in the majority of frameworks. Classes owe their popularity to the rich set of object-oriented features they support and to their general applicability. Base classes and abstract classes are special logical groups related to extensibility. Extensibility and base classes are covered in Chapter 6.
Interfaces are types that can be implemented both by reference types and value types. This allows them to serve as roots of polymorphic hierarchies of reference types and value types. In addition, interfaces can be used to simulate multiple inheritance, which is not natively supported by the CLR.
Structs are the general case of value types and should be reserved for small, simple types, similar to language primitives.
Enums are a special case of value types used to define short sets of values, such as days of the week, console colors, and so on.
Static classes are types intended as containers for static members. They are commonly used to provide shortcuts to other operations.
Delegates, exceptions, attributes, arrays, and collections are all special cases of reference types intended for specific uses, and guidelines for their design and usage are discussed elsewhere in this book.
Figure 4-1: The logical grouping of types
4.1 Types and Namespaces
Before designing a large framework you should decide how to factor your functionality into a set of functional areas represented by namespaces. This kind of top-down architectural design is important to ensure a coherent set of namespaces containing types that are well integrated, don't collide, and are not repetitive. Of course the namespace design process is iterative and it should be expected that the design will have to be tweaked as types are added to the namespaces over the course of several releases. This leads to the following guidelines.
4.1.1 Standard Subnamespace Names
Types that are rarely used should be placed in subnamespaces to avoid cluttering the main namespaces. We have identified several groups of types that should be separated from their main namespaces.
4.1.1.1
The .Design Subnamespace
Design-time-only types should reside in a subnamespace named .Design. For example, System.Windows.Forms.Design contains Designers and related classes used to do design of applications based on System. Windows.Forms.
System.Windows.Forms.Design System.Messaging.Design System.Diagnostics.Design
4.1.1.2
The .Permissions Subnamespace
Permission types should reside in a subnamespace named .Permissions.
4.1.1.3
The .Interop Subnamespace
Many frameworks need to support interoperability with legacy components. Due diligence should be used in designing interoperability from the ground up. However, the nature of the problem often requires that the shape and style of such interoperability APIs is often quite different from good managed framework design. Thus, it makes sense to put functionality related to interoperation with legacy components in a subnamespace.
You should not put types that completely abstract unmanaged concepts and expose them as managed into the Interop subnamespace. It is often the case that managed APIs are implemented by calling out to unmanaged code. For example the System.IO.FileStream class calls out to Win32 CreateFile. This is perfectly acceptable and does not imply that the FileStream class needs to be in System.IO.Interop namespace as FileStream completely abstracts the Win32 concepts and publicly exposes a nice managed abstraction.