Skip to content

Latest commit

 

History

History
89 lines (75 loc) · 11 KB

Class Libraries.md

File metadata and controls

89 lines (75 loc) · 11 KB

C# Class Libraries

As class libraries are typically intended to be used by a broad selection of developers who may not have insight into (or access to) the internal workings of the library, they introduce their own unique challenges. That said, while these are documented separately from our general C# Style Guide, Ignia considers these guidelines a best practice for all C# development.

Note: This style guide inherits rules from the C# Style Guide, C-Based Languages Style Guide, and the Global Style Guide.

Note: The majority of the items below are from the (excellent) book "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition) (Krzysztof Cwalina, Brad Abrams; 2008) which is required reading for all developers at Ignia. The items highlighted below are some of the most important and common conventions from the book.

Contents

Identifiers

  • Namespaces should use <Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>] convention; e.g., Ignia.Localization.Import (source)
  • Assemblies (DLLs) should generally be named <Company>.(<Product>|<Technology>)[.<Feature>].dll for consistency with namespaces; e.g., Ignia.Localization.Import.dll

Note: Ignia uses Ignia for the <company> on internal projects and reusable libraries, but the client name for custom client code

  • Namespace identifiers should typically be pluralized (exceptions: proper names, abbreviations) (source)
  • Do not use the same identifier for a namespace as well as a class (source)
  • "If your brand employs nontraditional casing, you should follow the casing defined by your brand, even if it deviates from normal namespace casing" (source)
  • Avoid generic identifier names that are likely to introduce conflicts with class names from related namespaces (source)
  • Interfaces should begin with I and use adjective phrases (e.g., ICacheable); nouns are more appropriate for abstract classes (e.g., Cache) (source)
  • Attribute, EventHandler, EventArgs, Exception, Collection, and Permission are all expected suffixes for corresponding class types (source)

Language Features

  • Only use public fields for constants; otherwise, expose properties (source)
  • Consider using extension methods as a means of providing concrete implementations for general interface methods (source)
  • Use the least derived type for method parameters; e.g., use an interface or base class when its properties are sufficient (source)
  • Avoid using out or ref types for parameters (source)
  • Consider using the params keyword for (short) array parameters; otherwise, place arrays as the last parameter in a method so this can be applied in the future, if needed (source)
  • For serialization, prefer Data Contract Serialization unless control over the XML schema is needed (then use XmlSerializer) or the objects will be used via .NET Remoting (then use Runtime Serialization) (source)
  • Avoid use of nested types

Collections

  • Dictionary<>, Hashtable, ArrayList, List<>, and IEnumerator objects should not be exposed publicly (source)
  • Exception: IEnumerator may be returned as the result of a GetEnumerator() method
  • Arrays should be avoided for similar reasons (source)
  • Use the least derived collection type for parameters; ideally, this will be the IEnumerable<T> interface (source)
  • Do not expose setters for collection properties; this allows the collection to be overwritten, not just records (source)
  • For writable properties and method return values, prefer Collection<>, ObservableCollection<> (or a derived class); otherwise, use classes that implement ICollection<>, IList<>, or IEnumerable<> (source)
    • When practical, use a custom derived class for clarity of purpose, and to allow additional functionality to be added in the future
    • When creating custom derived classes, prefer the naming convention TypeCollection where Type maps to the item type (e.g., AuthorCollection is a collection of Author objects)
    • When exposing collections as properties, do not include the Collection suffix; instead, pluralize the type name (e.g., Book.Authors)
  • For volatile collections (e.g., a file system object), return a snapshot (via a method) or an IEnumerable<> (via a property) to prevent exceptions during iteration
  • For read-only collections, always use ReadOnlyCollection<> (or a derived class)
  • Always return an empty collection rather than a null value for collection types; this makes it easier to test

Extensibility

  • Prefer events to callbacks (e.g., Func<>, Action<>, and Expression<>) as they are easier to understand and implement across languages (source)
  • Be wary of virtual members, as they provide potential security risks; when used, consider moving extensible functionality to a protected method to restrict scope (source)
    • When a key method delegates implementation details to a protected method for extensibility, be sure to note this in the documentation for the key method so the relationship is clear
  • Provide concrete classes for both abstract classes as well as interface definitions; this helps validate the design, and provides simpler options for casual implementations (source)
  • Consider providing reference tests for abstract classes and interface definitions to allow first- and third-party developers to easily test their implementations (source)

Exceptions

  • Avoid throwing exceptions from properties; any state checking should be done when an action is performed (e.g., by calling a method)
  • Validate arguments by throwing ArgumentException, ArgumentNullException, InvalidOperationException, or a derived class on error (source)
    • Always set the paramName property when throwing ArgumentException or ArgumentNullException; use value when validating property setters (source)
  • With the exception of unforeseeable problems (e.g., system failures), users should be able to pre-check conditions without throwing an exception via the Tester-Doer pattern (source)
    • e.g., the FileStream class offers a CanWrite property to conditionally determine if a stream can be written to
    • Fall back to the Try-Parse pattern for scenarios where providing pre-condition checks is not performant (e.g., a time-consuming conversion) (source)
  • Never throw a NullReferenceException, AccessViolationException, or IndexOutOfRangeException as resolving them relies on knowledge of the implementation; these should be caught as part of argument checking (source).
  • Do not throw or catch Exception or SystemException; use more specific exceptions instead (exception: top-level exception handlers) (source)

Enums

  • Prefer enums over arbitrary strings (e.g., Status="Complete") or static constants to set or receive values from a list of predefined (non-dynamic) choices
  • Prefer enums as method parameters to multiple Boolean arguments, or Boolean arguments which may have more options in the future (source)
  • Enum names should be singular, unless they implement the [flag] attribute; enums should not be suffixed with Enum or Flags (source)
  • Only set enum values for capabilities currently supported; do not reserve values for future use (source)
  • Always provide an enum value for None (or the semantic equivalent) (source)
  • Flag enums can be used to allow multiple values; assigned integers should be based on powers of two (2, 4, 8...) to allow proper integer serialization (source)

Unit Tests

  • Consider establishing a distinct unit test class for each class (e.g., MyClassTest for tests related to MyClass)
    • It is appropriate to combine related classes (e.g., collections, derived classes, &c.)
  • Where appropriate, name unit tests {Class}_{Member}{Test}; e.g., MyClass_SetValueTest to test MyClass.SetValue()
  • Be sure to test both with correct and incorrect input; e.g., ensuring the members fail as expected

Acknowledgments