csharp:S3453 should take into account tag dispatch

Right now S3453 marks as a bug every class with private only constructors which is never instantiated.

The reasoning behind (“A class with only private constructors can’t be instantiated, thus, it seems to be pointless code.”) is not true in general. “Tag dispatching” uses class names just to select the right implementation/overload/whatever and it never instantiate anything, making a class with a private constructor useful while not ever instantiated.

While this pattern is not widespread in C# code (more like a C++ idiom), it can be very useful using Microsoft injector to select the right configuration/implementation/provider when a service may have different settings in the same program.

An example follows:

Take for example these classes:

public class MyConfiguration
{
    public string ImportantSetting { get; set; }
}

public class MyServiceFactory
{
   private readonly IOptions<MyConfiguration> configuration;

   public MyServiceFactory(IOptions<MyConfiguration> configuration)
   {
      this.configuration = configuration;
   }

   public MyService Create() => new MyService(configuration.Value.ImportantSettings);
}

If I wanted two different instances of the factory with different configurations I would be forced to use magic strings or similar (something like Named Options).

I could instead change everything to:


public class MyConfiguration<TTag>
{
    public string ImportantSetting { get; set; }
}

public class MyServiceFactory<TTag>
{
   private readonly IOptions<MyConfiguration<TTag>> configuration;

   public MyServiceFactory(IOptions<MyConfiguration<TTag>> configuration)
   {
      this.configuration = configuration;
   }

   public MyService Create() => new MyService(configuration.Value.ImportantSettings);
}

And use it like:


public class Cool { private Cool () { } }
public class Boring { private Boring () { } }

// Later in services configuration

public void ConfigureServices()
{
   services
      .AddSingleton<MyServiceFactory<Cool>>()
      .Configure<MyConfigurarion<Cool>>(Configuration.GetSection("veryCool"))

      .AddSingleton<MyServiceFactory<Boring>>()
      .Configure<MyConfigurarion<Boring>>(Configuration.GetSection("boring"));
}

// And have services inject like

public class FirstCoolService
{
   public FirstCoolService(MyServiceFactory<Cool> coolFactory) { ... } 
}

public class SecondCoolService
{
   public FirstCoolService(MyServiceFactory<Cool> coolFactory) { ... } 
}

public class OnlyOneBoringService
{
   public FirstCoolService(MyServiceFactory<Boring> coolFactory) { ... } 
}

In this code, the private constructor is most certainly not a bug. Moreover, this code is refactor-friendly and also avoids name-clashes of magic strings in named options (pretty much useful while developing libraries). There are other minor advantages to this approach like: restrict manually-injected configurations, forcing an interface, hiding details, etc…

As an additional note, it is not possible in this case to use a static class since it cannot be used as a generic argument.

Hi @marco6, thank you for your interest in our product!

We’ve taken a look at the examples you’ve provided and agreed that we regard this more as a corner case. Since this is not currently not a priority for us, we are not planning to provide support for that.