1

I am stuck with a problem about interface covariance and contravariance. I have two generic interfaces:

public interface IConfigConsumer<T> where T : IConfiguration
{
    void Load(T configuration);
}

public interface IConfigProvider<out T> where T : IConfiguration
{
    T Configuration { get; }
}

IConfiguration is just an empty interface (marker). There is a third interface:

public interface IConfigurable<T> : IConfigConsumer<T>, IConfigProvider<T>
    where T : IConfiguration
{
    void Reset();
}

I would like to have a list with all the IConfigurable implementations (regardless of the concrete type of T). Since IConfigProvider is covariant I can have a list of IConfigProviders, but IConfigConsumer is invariant so I can't have a List like this:

var consumers = new List<IConfigConsumer<IConfiguration>>();

The best would be to have a list of IConfigurable<IConfiguration> since I will need the functionalities defined in both interfaces.

I just can't find a way to do this.

A workaround could be to just get rid of the type parameter in the IConfigConsumer interface and define the Load method as follows:

void Load(IConfiguration configuration)

But that would "hurt" the type safety a little bit since I could Load a type ConfigA into a ConfigConsumer that expects ConfigB and it would throw an exception only in runtime when I would try and cast it to ConfigB in the Load method.

Here is my complete sample code:

public interface IConfiguration
{
}

public interface IConfigurable<T> : IConfigConsumer<T>, IConfigProvider<T>
    where T : IConfiguration
{
    void Reset();
}

public interface IConfigConsumer<T> where T : IConfiguration
{
    void Load(T configuration);
}

public interface IConfigProvider<out T> where T : IConfiguration
{
    T Configuration { get; }
}

public abstract class ConfigBase : IConfiguration { }

public class TimeSynchronizationConfig : ConfigBase, ICloneable
{
    public static string[] DefaultNtpServers = new string[] { "0.pool.ntp.org", "time1.google.com" };

    public string NTPServer1 { get; set; } = DefaultNtpServers[0];
    public string NTPServer2 { get; set; } = DefaultNtpServers[1];

    public object Clone()
    {
        return new TimeSynchronizationConfig
        {
            NTPServer1 = NTPServer1,
            NTPServer2 = NTPServer2
        };
    }
}

public class DataPruningConfig : ConfigBase, ICloneable
{
    public const int DefaultPruningThresholdHours = 72;
    public int PruningThresholdHours { get; set; }

    public DataPruningConfig()
    {
        PruningThresholdHours = DefaultPruningThresholdHours;
    }

    public DataPruningConfig(int pruningThresholdHours)
    {
        PruningThresholdHours = pruningThresholdHours;
    }

    public object Clone()
    {
        return new DataPruningConfig(PruningThresholdHours);
    }
}

public class A : IConfigurable<TimeSynchronizationConfig>
{
    public TimeSynchronizationConfig Configuration => new TimeSynchronizationConfig();

    public void Load(TimeSynchronizationConfig configuration)
    {
        throw new NotImplementedException();
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }
}

public class B : IConfigurable<DataPruningConfig>
{
    public DataPruningConfig Configuration => new DataPruningConfig();

    public void Load(DataPruningConfig configuration)
    {
        throw new NotImplementedException();
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var configurables = new List<IConfigurable<IConfiguration>>();
        var a = new A();
        var b = new B();

        configurables.Add(a);
        configurables.Add(b);

        ProcessConfigurables(configurables);
    }

    static void ProcessConfigurables(IEnumerable<IConfigurable<IConfiguration>> configurables)
    {
        foreach (var c in configurables)
        {

        }
    }
}

I started to doubt what I want is even possible. Do you have any idea? Thank you in advance for all your help!

  • It's not possible, because it's not type safe. If it were possible, you could do: `configurables[0].Load(new DataPruningConfig())`. But first configureable is `A` which expects `TimeSynchronizationConfig` – Evk Mar 01 '18 at 08:35
  • @Evk Thank you! – Travis182HUN Mar 01 '18 at 13:00

0 Answers0