Ваша коллекция List<WatchableVariable<IComparable>>
.Вы пытаетесь добавить WatchableVariable<T>
к нему.
Это не разрешено из-за правил общего отклонения.Давайте представим, что ваш WatchableVariable<T>
класс определил этот метод:
public class WatchableVariable<T>
{
public void Set(T value);
}
Если у вас есть WatchableVariable<Foo>
, вы можете позвонить watchableVariable.Set(new Foo())
, но , а не watchableVariable.Set((IComparable)foo)
.Это имеет смысл.
Примите это к вашей ситуации, и кто-то может позвонить Add<Foo>("")
, поэтому Watchables
содержит WatchableVariable<Foo>
.Затем они получают доступ к Watchables[0]
, что дает им WatchableVariable<IComparable>
, и звонят Set((IComparable)bar)
.Это было бы небезопасно, так как мы ранее говорили, что WatchableVariable<Foo>
метод Set(T value)
будет принимать только Foo
, но мы только что дали ему любой старый IComparable
!
Способ обойтиэто использует ковариацию, которая доступна только на интерфейсах.
Во-первых, мы объявляем универсальный интерфейс, и мы обещаем, что вы можете когда-либо только извлечь из него T
и никогда не вставлять их,используя out T
:
public interface IWatchableVariable<out T>
{
// The compiler stops you from defining any methods which accept a T
}
public class WatchableVariable<T> : IWatchableVariable<T>
{
// ...
}
Тогда вы можете написать:
private List<IWatchableVariable<IComparable>> Watchables;
И все будет работать.
(Аналогичная вещь доступна с использованием контравариантности и in T
, но это не применимо к вашей ситуации).
В ответ на ваши правки 2
Ваша правка содержит комментарий
// Variable would be a property of type T (which would be double in this case)
Это утверждение во время выполнения.Вы должны будете определить свой интерфейс следующим образом:
public interface IWatchableVariable<out T>
{
object Variable { get; set; }
}
Поскольку Variable
не относится к типу T
, это разрешено.Тогда вашему классу WatchableVariable<T>
придется выполнить приведение во время выполнения, чтобы убедиться, что для Variable
задан совместимый тип.Вы можете использовать явную реализацию интерфейса, чтобы сделать это немного аккуратнее.
public class WatchableVariable<T>
{
public T Variable { get; set; }
object IWatchableVariable<T>.Variable
{
get => Variable;
set => Variable = (T)value;
}
}
Вы можете сделать это немного аккуратнее, выполнив что-то вроде:
public interface IWatchableVariable<out T>
{
T Variable { get; }
void SetVariable(object value);
}
public class WatchableVariable<T> : IWatchableVariable<T>
{
public T Variable { get; set; }
public void SetVariable(object value)
{
Variable = (T)value;
}
}