C# Class Design Standards · CS-02
A class or interface should have a single purpose · CS-02.1 · MUST
A class or interface should have a single purpose within the system it participates in. In general, a class is either representing a primitive type like an email or ISBN number, an abstraction of some business concept, a plain data structure or responsible for orchestrating the interaction between other classes. It is never a combination of those. This rule is widely known as the Single Responsibility Principle (SRP), one of the SOLID principles.
Roslyn Analyzer Rule AV1000
An interface should be small and focused · CS-02.2 · MUST
Interfaces should have a name that clearly explains the purpose or role of that interface within the system. Do not combine many vaguely related members on the same interface just because they were all on the same class. Separate the members based on the responsibility of those members so that callers only need to call or implement the interface related to a particular task. This rule is more commonly known as the Interface Segregation Principle (ISP), one of the SOLID principles.
Use an interface rather than a base class to support multiple implementations · CS-02.3 · SHOULD
If you want to expose an extension point from your class, expose it as an interface rather than a base class. You don’t want to force users of that extension point to derive their implementations from a base-class that might have undesired behavior. However, for their convenience you may implement an (abstract) default implementation that can serve as a starting point.
Use an interface to decouple classes from each other · CS-02.4 · MUST
Interfaces are a very effective mechanism for decoupling classes from each other because:
- they can prevent bidirectional associations;
- they simplify the replacement of one implementation with another;
- They allow replacing an expensive external service or resource with a temporary stub for use in a non-production environment;
- they allow replacing the actual implementation with a dummy implementation or a fake object in a unit test; and
- using a dependency injection framework you can centralize the choice which class is going to be used whenever a specific interface is requested.
Don't hide inherited members with the new keyword · CS-02.5 · MUST
new keyword · CS-02.5 · MUSTNot only does the new keyword break Polymorphism, one of the most essential object-orientation principles, it also makes subclasses more difficult to understand. Consider the following two classes:
public class Book
{
public virtual void Print()
{
Console.WriteLine("Printing Book");
}
}
public class PocketBook : Book
{
public new void Print()
{
Console.WriteLine("Printing PocketBook");
}
}This will cause behavior that you would not normally expect from class hierarchies:
var pocketBook = new PocketBook();
pocketBook.Print(); // Will output "Printing PocketBook "
((Book)pocketBook).Print(); // Will output "Printing Book"It MUST NOT make a difference whether you call Print through a reference to the base class or through the derived class.
Roslyn Analyzer Rule AV1010
Treat a derived object as if it were a base class object · CS-02.6 · SHOULD
In other words, you SHOULD be able to use a reference to an object of a derived class wherever a reference to its base class object is used without knowing the specific derived class. A very notorious example of a violation of this rule is throwing a NotImplementedException when overriding some of the base-class methods. A less subtle example is not honoring the behavior expected by the base-class. This rule is also known as the Liskov Substitution Principle (LSP), one of the SOLID principles.
Roslyn Analyzer Rule RCS1079(partial)
Don't refer to derived classes from the base class · CS-02.7 · MUST
Having dependencies from a base class to its sub-classes goes against proper object-oriented design and might prevent other developers from adding new derived classes.
Avoid exposing the other objects an object depends on · CS-02.8 · SHOULD
If you find yourself writing code like this then you might be violating the Law of Demeter (LoD).
someObject.SomeProperty.GetChild().Foo()An object should not expose any other classes it depends on because callers may misuse that exposed property or method to access the object behind it. By doing so, you allow calling code to become coupled to the class you are using, and thereby limiting the chance you can easily replace it in a future stage.
NOTE: Using a class designed with the Fluent Interface pattern like LINQ does seem to violate this rule, but it is simply returning itself so that method chaining is allowed.
EXCEPTION: Inversion of Control (or Dependency Injection) frameworks such as Unity, Autofac or Ninject often require you to expose a dependency as a public property. As long as this property is not used for anything else than dependency injection, it wouldn’t be considered as a violation.
Avoid bidirectional dependencies · CS-02.8 · MUST
This means that two classes know about each other’s public members or rely on each other’s internal behavior. Refactoring or replacing one of those two classes requires changes on both parties and may involve a lot of unexpected work. The most obvious way of breaking that dependency is introducing an interface for one of the classes and using dependency injection.
Use records for types that just contain data · CS-02.9 · MUST
Use records for DTOs and other types that encapsulate data rather than behaviour, and where value-based equality makes sense. They should generally be immutable, although records can contain mutable properties.
Follow the guidelines for maximum number of constructor parameters (see here) to decide when to use standard property syntax over positional syntax. So if you have a record with five properties, declare them as standard properties.
IMPORTANT: Although Entity Framework entities are data-centric types, they are not suitable for records (see here for more information).