rjmurillo / EffectiveCSharp.Analyzers

Many of the recommendations in the book Effective C#: 50 Specific Ways to Improve Your C# can be validated by Roslyn-based analyzers and code fixes.
MIT License
2 stars 1 forks source link

Item 14: Minimize duplicate initialization logic #57

Closed rjmurillo closed 3 weeks ago

rjmurillo commented 2 months ago

Related: https://github.com/BillWagner/EffectiveCSharpAnalyzers/issues/14

Category: Style This might be hard to analyze. It's looking for repeated init logic in multiple ctors.

Writing constructors often results in repetitive code. Developers may write the first constructor and then copy and paste code into other constructors to satisfy multiple overloads defined in a class. This practice leads to code duplication and potentially less efficient object code.

A better approach is to refactor common initialization logic into a single constructor. The C# compiler can optimize this pattern, recognizing constructor initializers as special syntax and removing duplicated variable initializers and base class constructor calls. This ensures that your final object executes the minimum amount of code required to properly initialize and reduces overall maintenance overhead.

In contrast, manually writing utility methods to handle shared initialization logic is not optimal. These methods prevent the compiler from applying its optimizations, leading to less efficient IL (Intermediate Language) code.

Furthermore, understanding the sequence of events during object initialization in C# is critical:

  1. Static variable storage is set to 0.
  2. Static variable initializers execute.
  3. Static constructors for the base class execute.
  4. The static constructor executes.
  5. Instance variable storage is set to 0.
  6. Instance variable initializers execute.
  7. The appropriate base class instance constructor executes.
  8. The instance constructor executes.

Subsequent instances of the same type skip steps 1–4, as the static initializers and constructors execute only once. Steps 6 and 7 are optimized so that constructor initializers cause the compiler to remove duplicate instructions.

Problem

Many classes have multiple constructors that repeat similar initialization logic, leading to duplicated code and potentially inefficient object creation. This issue arises when:

Solution

Refactor the code by using constructor initializers to chain constructors, thus consolidating common initialization logic in one place. This approach allows the C# compiler to optimize the code more effectively.

For example:

public class MyClass
{
    private List<ImportantData> coll;
    private string name;

    public MyClass() : this(0, string.Empty)
    {
    }

    public MyClass(int initialCount) : this(initialCount, string.Empty)
    {
    }

    public MyClass(int initialCount, string name)
    {
        coll = (initialCount > 0) ? new List<ImportantData>(initialCount) : new List<ImportantData>();
        this.name = name;
    }
}

instead of

public class MyClass
{
    private List<ImportantData> coll;
    private string name;

    public MyClass()
    {
        commonConstructor(0, string.Empty);
    }

    public MyClass(int initialCount)
    {
        commonConstructor(initialCount, string.Empty);
    }

    public MyClass(int initialCount, string name)
    {
        commonConstructor(initialCount, name);
    }

    private void commonConstructor(int count, string name)
    {
        coll = (count > 0) ? new List<ImportantData>(count) : new List<ImportantData>();
        this.name = name;
    }
}

The goals of this analyzer are:

  1. Point out redundant code when initializing objects a. Duplicating logic in the body of a constructor or method b. Duplicating logic in member assignment / initialization and constructor body

Guidance from the author: