【问题标题】:How to store Action delegates with constrained generic type in a type safe collection in C#?如何在 C# 的类型安全集合中存储具有约束泛型类型的动作委托?
【发布时间】:2015-05-29 21:45:42
【问题描述】:

假设我想将委托存储在这样的集合中:

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

_delegates 的类型应该是什么?

尝试IList&lt;Action&lt;ISomething&gt;&gt; _delegates; 导致上述Add 调用的错误消息Argument type 'System.Action&lt;T&gt;' is not assignable to parameter type 'System.Action&lt;ISomething&gt;'。但为什么它不起作用?编译器应该“知道”T 必须是ISomething。 如果我不想通过使用例如失去类型安全性,我有什么选择? IList&lt;object&gt; 或在泛型类型 T 上参数化整个类?

【问题讨论】:

  • 你能把集合包装在public class DelegateCollection&lt;T&gt; where T : ISomething吗?
  • @DavidArno 有更好的答案

标签: c# generics delegates


【解决方案1】:

_delegates 必须是 IList&lt;Action&lt;T&gt;&gt; 类型。

因此您必须将&lt;T&gt; where T : ISomething 添加到类中。

或者,取消泛型并直接支持Action&lt;ISomething&gt;

所以你的两个选择是:

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

编辑 As Sehnsucht points out, there's a third option:将 Action&lt;T&gt; 委托包装在 Action&lt;ISomething&gt; 委托中,然后 OP 就可以实现他最初想要的。

原因是,虽然TISomething 的“子类型”(实现者),但Action&lt;T&gt; 不是Action&lt;ISomething&gt; 的子类型,事实上as dcastro explains in his answer,实际上正好相反(尽管这似乎违反直觉)。即使使用强制转换,您也无法将 Action&lt;T&gt; 的实例添加到 Action&lt;ISomething&gt; 的列表中。

【讨论】:

    【解决方案2】:

    您应该查看here 以更深入地了解问题

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

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

    【讨论】:

    • 我不会称之为黑客。国际海事组织是一件好事。我将更新我的答案以将此作为第三个选项。
    • 这实际上是一个非常聪明的解决方法,谢谢!
    【解决方案3】:

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

    Action&lt;in T&gt;T 中是逆变的,这意味着Action&lt;Derived&gt; 不是Action&lt;Base&gt; 的子类型,即使DerivedBase 的子类型。

    事实上,逆变的意思正好相反:Action&lt;Derived&gt;Action&lt;Base&gt;超类型。也就是说,Action&lt;object&gt; 可以分配给Action&lt;string&gt;

    在您的情况下,Action&lt;T&gt; where T : ISomething 不能分配给 Action&lt;ISomething&gt;,但相反的情况是可能的。

    【讨论】:

    • @HimBromBeere 确实如此。 OP 提出了一个两部分的问题:为什么这不起作用,我有什么选择?我已经回答了第一部分,David Arno 回答了第二部分。
    【解决方案4】:

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

    这是不安全的,例如:

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

    Action&lt;T&gt;T 中是逆变的,这意味着如果UT 的子类型,它只能分配给Action&lt;U&gt;,因为ISomething 是超类型,所以在你的情况下不是这样T.

    【讨论】:

      猜你喜欢
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多