Контравариантные или инвариантные интерфейсы в одном и том же списке в C#
Я застрял с проблемой ковариации и контравариантности интерфейса. У меня есть два общих интерфейса:
public interface IConfigConsumer<T> where T : IConfiguration
{
void Load(T configuration);
}
public interface IConfigProvider<out T> where T : IConfiguration
{
T Configuration { get; }
}
IConfiguration - это просто пустой интерфейс (маркер). Есть третий интерфейс:
public interface IConfigurable<T> : IConfigConsumer<T>, IConfigProvider<T>
where T : IConfiguration
{
void Reset();
}
Я хотел бы иметь список со всеми реализациями IConfigurable (независимо от конкретного типа T). Поскольку IConfigProvider является ковариантным, у меня может быть список IConfigProvider, но IConfigConsumer является инвариантным, поэтому у меня не может быть такого списка:
var consumers = new List<IConfigConsumer<IConfiguration>>();
Лучше всего было бы иметь список IConfigurable<IConfiguration>
так как мне понадобятся функциональные возможности, определенные в обоих интерфейсах.
Я просто не могу найти способ сделать это.
Обходным путем может быть просто избавиться от параметра type в интерфейсе IConfigConsumer и определить метод Load следующим образом:
void Load(IConfiguration configuration)
Но это немного "повредит" безопасности типов, так как я могу загрузить тип ConfigA в ConfigConsumer, который ожидает ConfigB, и он выдаст исключение только во время выполнения, когда я попытаюсь привести его к ConfigB в методе Load.
Вот мой полный пример кода:
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)
{
}
}
}
Я начал сомневаться в том, что я хочу, даже возможно. Есть ли у вас какие-либо идеи? Заранее благодарю за помощь!