SoFunction
Updated on 2025-03-06

C# type field and method design suggestions

1. Do not provide public constructors for abstract classes

Abstract classes can have constructors, but abstract classes cannot be instantiated. If the programmer does not formulate a constructor, the compiler will automatically generate a default protected constructor. Here is a standard simple abstract class:

abstract class MyAbstractClass
{
 protected MyAbstractClass( ) { }
}

The constructor of an abstract class should not be public or internal. The original intention of abstract class design is to allow subclasses to inherit, rather than to generate instance objects. If the abstract class is public or internal, it is visible to other types, which is unnecessary and redundant. An abstract class only needs to be visible to subclasses.

2. The visible field should be refactored into attributes

There is an essential difference between fields and attributes, and attributes are methods. Like the following Person type:

class Person
{
 public string Name { get; set; }
}

After the compiler compiles for the property Name, it generates a field and two methods.

Attributes have the following advantages over fields:

1) You can add code to the attribute. Attributes are methods, so you can write code control within the method for setting or obtaining attributes. Such as event support, etc.

2) It is possible to make attributes support thread safety. To make attributes thread-safe, you can enable the type itself. If the fields support thread safety, they can only rely on the caller themselves to achieve it.

3) Attributes are supported by VS compiler, and can realize the function of automatic attributes. The characteristics of automatic attributes are widely used in LINQ. In anonymous types, it can only implement read-only automatic attributes, but fields do not support them.

4) From a design perspective (object-oriented), exposed fields should also use properties. When changing the status of the field, the type will not be notified; when changing the value of the property, the type support will be notified.

In summary, if a type has a visible field, it should be refactored as a property. If a property is only visible to the inside but does not involve the above 4 points, it is recommended to use fields.

3. Treat override and new differently

override and new make the type system appear polymorphic due to inheritance. Polymorphism is one of the three important characteristics of "object-oriented language". Polymorphism requires that subclasses have methods with the same name as base class methods, while override and new have the following functions:

1) If a method in a subclass is preceded by the new keyword, the method is defined as a method independent of the base class.

2) If the method in the subclass is preceded by the override keyword, the subclass object will call the method instead of the base class's method.

If the new keyword is used in the child class for the method of the parent class, the two methods are independent of each other. At this time, when using the subclass object to call the method, the program will execute the method code of the subclass type new; and if the subclass type is converted to the parent class type, the method code of the parent class will be executed when the object calls the method.

If the override keyword is used to override the method, the subclass code will be executed when calling the method regardless of whether the object of the subclass type is converted to the parent class type.

If for a subclass, a method that declares the same function name as the parent class, but does not use the keywords new and override. The compiler will issue a warning after compilation, but it will not affect the program's operation. At this time, the compiler will default to the new effect, so the output and display settings are the same as the new effect.

4. Avoid calling virtual members in constructor methods

Calling virtual members in constructors will cause unexpected errors.

class Program
 {
  static void Main(string[] args)
  {
   Chinese chinese = new Chinese();
  }
 }

 class Person
 {
  public Person()
  {
   InitSkin();
  }

  protected virtual void InitSkin()
  {
   //Omitted  }
 }

 class Chinese:Person
 {
  Rece Rece;

  public Chinese():base()
  {
   Rece = new Rece() { Name = "Zhao Ming" };
  }

  protected override void InitSkin( )
  {
   ();
  }
 }

 class Rece
 {
  public string Name { get; set; }
 }

Running the example, NullReferenceException: The object reference is not set to the instance of the object.

In the calling code, you need to create an instance object of Chinese chinese. Since the Chinese type has the base class Person, the base class constructor is first called when running. In the base class constructor, the constructor calls the InitSkin virtual method. When the program is running, the InitSkin method of the subclass is called. The subclass Rece variable is used in the InitSkin method of the subclass. But at this time, the constructor of the subclass has not been called yet, so the Rece variable is not instantiated, but the InitSkin method is using the Rece variable again, resulting in an error.

5. Members should give priority to the disclosed base type or interface

Type members prioritize exposing base types or interfaces, which will make the types support more application scenarios.

The set types in FCL are divided into List<T>, Dictionary<TKey, TValue>, HashSet<T>, etc. according to the function. For example, we need to clear the elements in the collection and return the method Empty of the empty collection. If we do not return the base type or interface, we are required to implement this method for each collection type. However, a statically typed Enumerable is implemented in FCL, with the following code:

 public static IEnumerable<TResult> Empty<TResult>()
 {
  return EmptyEnumerable<TResult>.Instance;
 }

The generic interface IEnumerable is used, so all collection subclasses can not implement their own Empty methods, so that the project can be flexible.

6. Subclass parameters should not be used during rewriting

When rewriting, if subclass parameters are used, it may deviate from the designer's intended goal.

If the following inheritance system exists:

class Employee
{
}

class Manager:Employee
{
}

class Salary
{
 public void SetSalary(Employee e)
 {
  ("The staff is paid.");
 }
}  

class ManagerSalary:Salary
{
 public void SetSalary(Manager e)
 {
  ("The manager was paid.");
 }
}

The SetSalary method in type ManagerSalary overrides the same method in Salary, but the parameters take parameters of a subclass. Now call the code in the program as follows:

public static void Main()
{
 ManagerSalary m = new ManagerSalary();
 (new Employee());
}

The designer's original intention is to set the corresponding salary for the manager, but the actual code called set the employee's salary. Therefore, there is some risk of using subclass parameters when rewriting. The correct method is still used to use the Employee type parameter, so that the compiler reminds us to use the keyword new.

The above is the detailed content of the C# field and method design suggestions. For more information about C# fields and methods, please pay attention to my other related articles!