简体   繁体   中英

C# generic object cannot be added to list of that generic type

Sorry if question name wasn't that explicit, but I couln't find a better one...

Here's my problem :

I have a class :

public class WatchableVariable<T>{
  ...
}

A Container Class :

public class CommonWatchableVariableContainer
{
  private List<WatchableVariable<IComparable>> Watchables;

  ...

  public void Add<T>(string name) where T : IComparable
  {
    WatchableVariable<T> test = new WatchableVariable<T>(name);
    Watchables.Add(test);
  }

  ...
}

And have a strange error :

在此处输入图片说明

Which, IMO, should work since i'm applying a "IComparable" constraint on my generic type.

could someone point me out where I might have messed up ?

EDIT : I already tried to implement IComparable on Class WatchableVariable which resulted in the exact same error :/

public class WatchableVariable<T> : ObservableObject where T : IComparable
  { ... }

EDIT 2 : While trying to implement the solution proposed by canton7, I realized I may be blocked.

Here's the interface I created :

public interface IWatchableVariable<out T>
{
  T Variable { get; set; }
}

Here's what I'm trying to achieve :

double dVar1 = process();
double dVar2 = process();
double dVar3 = process();

bool bVar1 = process();

CommonWatchableVariableContainer container = new CommonWatchableVariableContainer ();
container.Add<bool>("myTrackedVariable_1");
container.Add<double>("myTrackedVariable_2");

container["myTrackedVariable_1"].Variable = dVar1; //Variable would be a property of type T (which would be double in this case)
container["myTrackedVariable_2"].Variable = bVar1;

foreach(IWatchableVariable WV in container){
  process(WV);
}

container.Remove("myTrackedVariable_1");

Does this make things clearer ?

Your collection is a List<WatchableVariable<IComparable>> . You're trying to add a WatchableVariable<T> to it.

This is not allowed, because of the rules of generic variance. Let's pretend that your WatchableVariable<T> class defined this method:

public class WatchableVariable<T>
{
    public void Set(T value);
}

If you've got a WatchableVariable<Foo> , you can call watchableVariable.Set(new Foo()) , but not watchableVariable.Set((IComparable)foo) . That makes sense.

Take that to your situation, and someone could call Add<Foo>("") , so Watchables contains a WatchableVariable<Foo> . They then access Watchables[0] , which gives them a WatchableVariable<IComparable> , and call Set((IComparable)bar) . This would be unsafe, as we previously said that a WatchableVariable<Foo> 's Set(T value) method will only accept a Foo , but we've just given it any old IComparable !

The way around this is using covariance, which is only available on interfaces.

First, we declare a generic interface, and we promise that you can only ever take T 's out of it, and never put them in, using 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>
{
    // ...
}

Then you can write:

private List<IWatchableVariable<IComparable>> Watchables;

And everything will work.

(A similar thing is available using contravariance and in T , but that isn't applicable to your situation).


In response to your Edit 2

Your edit contains the comment

// Variable would be a property of type T (which would be double in this case)

That's a run-time assertion. You will have to define your interface like this:

public interface IWatchableVariable<out T>
{
    object Variable { get; set; }
}

Since Variable isn't of type T , this is allowed. Then your WatchableVariable<T> class will have to do a run-time cast, to make sure that Variable was set to a compatible type. You can use an explicit interface implementation to make this a bit neater.

public class WatchableVariable<T>
{
    public T Variable { get; set; }

    object IWatchableVariable<T>.Variable
    {
        get => Variable;
        set => Variable = (T)value;
    }
}

You can make this slightly neater by doing something like:

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;
    }
}

I don't know what are you trying to achieve, but to make that work you should: declare your type with an IComparable constraint.

public class WatchableVariable<T> where T : IComparable
{
    public WatchableVariable(string name) { }
}

Then make your second class generic with the same constraint.

public class CommonWatchableVariableContainer<T> where T : IComparable
{
    private List<WatchableVariable<T>> Watchables;

    public void Add(string name)
    {
        var test = new WatchableVariable<T>(name);
        Watchables.Add(test);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM