2

I have the following class definition:

public class Registry
{
     private List<Action<IThing>> _thingActions = new List<Action<IThing>>();

     public Register<TDerivedThing>(Action<TDerivedThing> thingAction)
          where TDerivedThing : IThing
     {
        // this line causes compilation issue
        _thingActions.Add(thingAction);
     }     
}

Why does this complain that it cannot assign Action<TDerivedThing> to Action<IThing>, and how should I go about resolving this?

BlakeH
  • 3,354
  • 2
  • 21
  • 31
  • 1
    You can't, this is not typesafe. – millimoose Apr 17 '13 at 13:22
  • Dumb question. Does TDerivedThing implement the interface? – Stian Standahl Apr 17 '13 at 13:22
  • It's a covariance limitation. See here: http://stackoverflow.com/questions/1078423/c-sharp-is-variance-covariance-contravariance-another-word-for-polymorphis – kemiller2002 Apr 17 '13 at 13:24
  • Presumably you're going to iterate through `_thingActions` running them on some `IThing`? What happens when your `IThing` isn't a `TDerivedThing`? – George Duckett Apr 17 '13 at 13:24
  • @StianStandahl It does... the where should denote that – BlakeH Apr 17 '13 at 13:26
  • Just from design perspective, why are you not accepting Action delegate as input parameter of method? You will always need to cast input delegate to Action. If you accept delegate of Action then tomorrow if you need to accept delegate of another derived class type, you would not require to make code change here. the casting would be required to be done on caller side. – RockWorld Apr 17 '13 at 13:40

3 Answers3

7

If C# allowed you to do this, code such as this would be valid:

interface IFruit {
    void Eat();
}

class Apple : IFruit {
    // ...
    void EatApple();
}

class Pear : IFruit {
    // ...
    void EatPear();
}

void EatApple(Apple a) {
    a.EatApple();
}

void EatPear(Pear p) {
    p.EatPear();
}

Action<IFruit> fruitAction = EatApple;
fruitAction(new Pear()); // Calls EatApple(new Pear())

The cast is invalid because it shouldn't be and you shouldn't try to do it in the first place. Your list of IThing actions all must be actions that accept any fruit.

millimoose
  • 39,073
  • 9
  • 82
  • 134
1

Of course not, List<EventArgs> cannot be assigned to List<object> as well, even though EventArgs is an object.

 public void Register<TDerivedThing>(Action<TDerivedThing> thingAction)
      where TDerivedThing : IThing
 {
    _thingActions.Add(t => thingAction(t as TDerivedThing));
 }

could be a solution, but i don't know your application.

esskar
  • 10,638
  • 3
  • 36
  • 57
1

That's a covariance and contrvariance issue. I recomend you to read the msdn article about it. It has a full answer to your question.

Andrei Zubov
  • 578
  • 4
  • 14