【问题标题】:Is it possible to create a delegate for *any* Action<T>? [duplicate]是否可以为 *any* Action<T> 创建一个委托? [复制]
【发布时间】:2014-06-21 13:43:58
【问题描述】:

我试图弄清楚为什么这不起作用:

public void DefaultAction( object obj = null ){}

public void Start()
{
    SomeReferenceType obj;
    DefaultAction( obj ); //works

    int i;
    string s;
    DefaultAction( i ); //works
    DefaultAction( s ); //works
}

//however...

public event Action OnNullAction = DefaultAction; //works
public event Action<SomeReferenceType> OnObjectAction = DefaultAction; //works
public event Action<int> OnIntAction = DefaultAction; //doesn't work!!

尝试将 void(object) 绑定到 Action&lt;ValueType&gt; 会引发参数不匹配错误,即使您可以使用 int/string/bool 直接调用该函数。是否发生了一些神秘的装箱/拆箱?不管怎样,是否有可能创建一个可以响应任何Action&lt;T&gt; 的委托?

【问题讨论】:

  • int 必须被装箱才能传递给Action&lt;object&gt;,这并不神秘。
  • 这是泛型逆变的限制,见stackoverflow.com/questions/12454794/…
  • public event Action OnNullAction = DefaultAction 这不行。
  • 我已将其作为副本关闭,但请参阅下面的答案以了解更多详细信息,以防您不清楚协变和逆变的概念。

标签: c# events parameters delegates action


【解决方案1】:

您发现委托逆变需要引用类型

我知道,那是相当高调的。

首先,让我清楚地说明什么是协变和逆变。假设您有类型之间的关系:“Giraffe 类型的值可以分配给Animal 类型的变量”。让我们记为

Animal <-- Giraffe

如果C&lt;that type&gt; 替换每个类型以保留箭头的方向,则称泛型类型C&lt;T&gt;T 中的协变

IEnumerable<Animal> <-- IEnumerable<Giraffe>

从 C# 4.0 开始,当我将此功能添加到语言中时,您可以在任何需要动物序列的地方使用长颈鹿序列。

如果替换反转箭头的方向,则称泛型类型C&lt;T&gt;T中是逆变的

Action<Animal> --> Action<Giraffe>

如果你需要一个动作,要求你给它一个Giraffe,并且你有一个动作可以接受任何Animal,那么你就准备好了;您需要可以使用GiraffeAction&lt;Animal&gt; 可以使用Giraffe 的东西。但这不是协变的。如果您手头有Action&lt;Giraffe&gt; 并且需要Action&lt;Animal&gt;,则不能使用Action&lt;Giraffe&gt;,因为*您可以将Tiger 传递给Action&lt;Animal&gt;,但不能传递Action&lt;Giraffe&gt;

Func&lt;T&gt; 呢?它在T 中是协变的。如果你需要一个返回 Animal 的函数并且你有一个返回 Giraffe 的函数,那很好,因为 Giraffe 将是 Animal

Func&lt;A, R&gt; 呢?它A 中是逆变的,在R 中是协变的。原因应该很清楚。

既然我们知道泛型类型的协变和逆变是什么,那么 C# 中的规则是什么?规则是:

  • 类型声明必须用in(逆变)和out(协变)注释。例如,delegate R Func&lt;in A, out R&gt;(A a)。请注意,in进入函数的东西,out函数的东西;我们故意将它们命名为 inout

  • 编译器必须能够证明注解是安全的。有关详细信息,请参阅规范或我的博客。

  • 泛型委托和接口支持变体,泛型结构、枚举或类不支持。

  • 不同的类型必须都是引用类型。

所以现在我们来回答您的问题。为什么它们都必须是引用类型?你推断出答案:拳击指导在哪里?

Action<object> oa = (object x)=>whatever;
Action<int> ia = oa; // Suppose this works.
ia(123);

拳击说明在哪里?不在分配给oa 的lambda 的主体中——那个东西已经有了object。不在对ia(123) 的调用中——那个东西需要一个整数。唯一可能的解决方案是oaia 不相等;这是一个简写

Action<object> oa = (object x)=>{whatever};
Action<int> ia = (int x)=>{ oa(x); };

但如果这就是你的意思,那就这么说吧。人们期望引用转换将保持引用身份,因此 C# 禁止必须对值进行装箱或拆箱的协变或逆变转换。

如果您对此有更多疑问,请在我的旧博客 (blogs.msdn.com/ericlippert) 中搜索 covariance 或搜索 C# 协方差常见问题解答。

【讨论】:

  • 问题是“是否可以为 any Action 创建委托?”。这不是“为什么我不能将 Action 转换为 Action?”。
  • @Eric 感谢您对为什么这不起作用的精彩解释。 Atomosk 感谢您提供简单的解决方法:)
  • @Atomosk:上述问题是不连贯的。我无法弄清楚“是否有可能为任何Action&lt;T&gt; 甚至 means 创建一个委托。Action&lt;T&gt; is 一个委托,所以这个问题是荒谬的。我没有试图解决这个荒谬的问题,而是选择解决另一个问题:是否发生了某种拳击?以及隐含的问题为什么存在类型不匹配?,因为这些实际上是有答案的问题。
  • Func&lt;T&gt;T 中不是协变的吗?
  • @T.C.:是的;谢谢。我修正了错字。
【解决方案2】:

让它通用

public void DefaultAction<T>(T param) { }

【讨论】:

  • 谢谢!这正是我需要的。
猜你喜欢
  • 2017-07-02
  • 1970-01-01
  • 1970-01-01
  • 2012-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 1970-01-01
相关资源
最近更新 更多