如何在c#的类型安全集合中存储具有约束泛型类型的Action委托
本文关键字:约束 泛型类型 委托 Action 存储 类型安全 集合 | 更新日期: 2023-09-27 17:51:04
假设我想将委托存储在这样的集合中:
public void AddDelegate<T>(Action<T> action) where T : ISomething
{
_delegates.Add(action);
}
_delegates
的类型应该是什么?
尝试使用IList<Action<ISomething>> _delegates;
导致错误消息 Argument type 'System.Action<T>' is not assignable to parameter type 'System.Action<ISomething>'
对于上面的Add
调用。但是为什么它不起作用呢?编译器应该"知道"T必须是ISomething
。如果我不想通过使用例如IList<object>
或在泛型T
上参数化整个类来失去类型安全性,我的选择是什么?
_delegates
必须为IList<Action<T>>
类型。
因此,您必须将<T> where T : ISomething
添加到类中。
或者,去掉泛型,直接支持Action<ISomething>
。
所以你的两个选择是:
public class Delegates<T> where T : ISomething
{
private List<Action<T>> _delegates;
public void AddDelegate(Action<T> action)
{
_delegates.Add(action);
}
}
或
public class Delegates
{
private List<Action<ISomething>> _delegates;
public void AddDelegate(Action<ISomething> action)
{
_delegates.Add(action);
}
}
编辑正如Sehnsucht指出的,还有第三种选择:将Action<T>
委托包装在Action<ISomething>
委托中,然后OP可以实现他最初想要的。
这样做的原因是,虽然T
是ISomething
的"子类型"(实施者),但Action<T>
不是Action<ISomething>
的子类型,事实上,正如dcastro在他的回答中解释的那样,事实恰恰相反(尽管这似乎违反直觉)。即使使用强制类型转换,也不能将Action<T>
的实例添加到Action<ISomething>
的列表中。
您应该在这里查看更多有关问题的信息
我可以提出一种"hack"来规避这个问题,但我不能说这是一件好事还是仅仅是一种hack。
void Add<T> (Action<T> action) where T : ISomething
{
Action<ISomething> typedAction = something => action ((T) something);
_delegates.Add (typedAction);
}
但是为什么它不起作用呢?编译器应该"知道"T必须是一个"等"。
Action<in T>
在T
中是逆变的,即Derived
是Base
的子类型,但Action<Derived>
不是Action<Base>
的子类型。
实际上,逆变的意思正好相反:Action<Derived>
是Action<Base>
的超类型。也就是说,Action<object>
可以分配给Action<string>
。
在您的例子中,Action<T> where T : ISomething
不能分配给Action<ISomething>
,但相反的情况是可能的。
您的示例将不起作用,因为虽然编译器知道T
是ISomething
,但它不知道将提供ISomething
的哪个子类型。只有当提供的类型始终是参数类型
这是不安全的,例如:
class FirstSomething : ISomething { }
class SecondSomething : ISomething { }
Action<FirstSomething> act = f => { }
Action<ISomething> sa = act; //not allowed
sa(new SecondSomething()); //unsafe!
Action<T>
在T
中是逆变的,这意味着如果U
是T
的子类型,它只能分配给Action<U>
,这不是在你的情况下,因为ISomething
是T
的超类型。