如何在C#中的类型安全集合中存储具有约束generics类型的Action委托?

假设我想将代理存储在这样的集合中:

public void AddDelegate(Action action) where T : ISomething { _delegates.Add(action); } 

_delegates的类型应该是什么?

尝试使用IList<Action> _delegates; 导致错误消息Argument type 'System.Action' is not assignable to parameter type 'System.Action'上面的Add调用的Argument type 'System.Action' is not assignable to parameter type 'System.Action' 。 但为什么不起作用? 编译器应该“知道”T必须是ISomething 。 如果我不想通过使用例如IList或参数化generics类型T上的整个类来放弃类型安全,我有哪些选择?

_delegates必须是IList>

因此,你必须在课堂上添加 where T : ISomething

或者,取消generics并直接支持Action

所以你的两个选择是:

 public class Delegates where T : ISomething { private List> _delegates; public void AddDelegate(Action action) { _delegates.Add(action); } } 

要么

 public class Delegates { private List> _delegates; public void AddDelegate(Action action) { _delegates.Add(action); } } 

编辑 如Sehnsucht所指出的,还有第三种选择 :将Action委托包装在Action委托中,然后OP可以实现他最初想要的东西。

原因是虽然TISomething的“子类型”(实现者),但Action不是Action的子类型,事实上正如dcastro在他的回答中所解释的那样 ,事实恰恰相反(尽管如此)看似违反直觉)。 即使使用强制转换,也无法将Action的实例添加到Action列表中。

但为什么不起作用? 编译器应该“知道”T必须是ISomething。

Action是逆变的,这意味着Action不是Action的子类型,即使DerivedBase的子类型。

实际上,逆向意味着相反: ActionAction超类型 。 也就是说,可以将Action分配给Action

在您的情况下, Action where T : ISomething无法分配给Action ,但相反的情况是可能的。

你应该在这里看到更深入的问题

我可以提出“一种黑客”来规避这个问题,但我不能说这是好事还是真的只是一个黑客。

  void Add (Action action) where T : ISomething { Action typedAction = something => action ((T) something); _delegates.Add (typedAction); } 

您的示例将无法工作,因为虽然编译器知道TISomething但它不知道将提供哪种 ISomething子类型。 如果提供的类型始终是参数类型的子类型,则只能安全地完成

例如,这将是不安全的:

 class FirstSomething : ISomething { } class SecondSomething : ISomething { } Action act = f => { } Action sa = act; //not allowed sa(new SecondSomething()); //unsafe! 

ActionT中是逆变的,这意味着如果UT的子类型,它只能被赋值给Action ,因为ISomethingT的超类型,所以它不在你的情况下。