Search code examples
c#genericsextension-methodsasp.net-core-7.0

Can't access static property in generic type but can in interface


When I inject options, I use the following configuration approach.

IHost host = Host.CreateDefaultBuilder(args)
  .ConfigureServices((host, services) =>
  {
    IConfiguration config = host.Configuration;
    ...
    services.Configure<SomeConfig>(
      config.GetSection(new SomeConfig().SectionKey));
  }).Build();

I have, of course, the interface and implementing class like so.

public interface IConfig
{
  public string SectionKey { get; }
}

public class SomeConfig : IConfig
{
  public string SectionKey => "SomeConfig";
  ...
}

Now, it's a minor issue but it still bugs me that the SectionKey property isn't static. It will after all be exactly the same in each instance of the class. Hence, I've tried to appoach it as follows.

IHost host = Host.CreateDefaultBuilder(args)
  .ConfigureServices((host, services) =>
  {
    IConfiguration config = host.Configuration;
    ...
    services.Configure<SomeConfig>(
      config.GetSection(SomeConfig.SectionKey));
  }).Build();

public interface IConfig
{
  public static string SectionKey { get; }
}

public class SomeConfig : IConfig
{
  public static string SectionKey => "SomeConfig";
  ...
}

The natural step is now to create an extension method so I'll be able to execute my configurations' injection like this.

IHost host = Host.CreateDefaultBuilder(args)
  .ConfigureServices((host, services) =>
  {
    IConfiguration config = host.Configuration;
    ...
    services.Config<SomeConfig>(config);
  }).Build();

Neat, right? However, I get stuck in the creation of the extention method because it works only if I create an instance of T and get the SectionKey value that's non-static.

public static class ConfigurationExtensions
{
  public static IServiceCollection Config<T>(
    this IServiceCollection self, IConfiguration config)
    where T : class, IConfig, new()
  {
    string key = new T().SectionKey;
    IServiceCollection output = self.Configure<T>(
      config.GetSection(key));

    return output;
  }
}

What I'd see as more optimal, is to be able to pull out the value of the SectionKey as an instance variable or, maybe even better, as a constant field! But it seems that the setup below fails.

public interface IConfig
{
  public string SectionKey { get; }
  public static string SectionKeyStatic { get; } = default!;
  public const string SectionKeyConst = default!;
}

public class SomeConfig : IConfig
{
  public string SectionKey => "SomeConfig";
  public static string SectionKeyStatic => "SomeConfig";
  public const string SectionKeyConst = "SomeConfig";
}

When I try to access the value of the key, the first two line work well, while the last two won't.

public static class ConfigurationExtensions
{
  public static IServiceCollection Config<T>(
    this IServiceCollection self, IConfiguration config)
    where T : class, IConfig, new()
  {
    string thisWorks = IConfig.SectionKeyStatic;
    string thisToo = IConfig.SectionKeyConst;
    string thisFails = T.SectionKeyStatic;
    string thisAlso = T.SectionKeyConst;
    ...
  }
}

I can't explain why nor how to resolve it. So for the time being I'm using the original strategy and live on bugged to the bones.


Solution

  • I found this document that describes the breaking change in .NET 6 (generally available in 7+) which allows for having abstract static interface members for reasons that sound very much like your use case. 😀

    Quote:

    This change was introduced because there was no way to abstract over static members and write generalized code that applies across types that define those static members.

    https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/static-abstract-interface-methods

    And related: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members#static-abstract-interface-methods