如何在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上参数化整个类来失去类型安全性,我的选择是什么?

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

_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可以实现他最初想要的。

这样做的原因是,虽然TISomething的"子类型"(实施者),但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中是逆变的,即DerivedBase的子类型,但Action<Derived>不是Action<Base>的子类型。

实际上,逆变的意思正好相反:Action<Derived>Action<Base>超类型。也就是说,Action<object>可以分配给Action<string>

在您的例子中,Action<T> where T : ISomething不能分配给Action<ISomething>,但相反的情况是可能的。

您的示例将不起作用,因为虽然编译器知道TISomething,但它不知道将提供ISomething的哪个子类型。只有当提供的类型始终是参数类型

的子类型时,才能安全地完成此操作。

这是不安全的,例如:

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

Action<T>T中是逆变的,这意味着如果UT的子类型,它只能分配给Action<U>,这不是在你的情况下,因为ISomethingT的超类型。