如何在C#中从Action<T>委托调用非静态方法

4

由于我正在编写一个通用概念,以便执行某些操作,因此需要在Action委托中调用一些非静态方法。 此外,我的代码中没有任何静态方法。 但是,我仍然无法在Action定义内部调用非静态方法。以下是我的代码-

private Dictionary<string, Action<object>> m_dicUndoRedoAction = new Dictionary<string, Action<object>>();
m_dicUndoRedoAction.Add("DeleteClass", DeleteClassFromeNode );

这里是DeleteClass的定义

private Action<object> DeleteClassFromeNode =
  data =>
  {
    Tuple<itemType1, itemType2> items = data as Tuple<itemType1, itemType2>;
    if (items != null && items.Item2 != null)
    {
      DeleteClass(items.Item2); // This is my non static method in the same class.
    }
  };

这是我调用委托的方式

private void Undo_Executed(object sender, ExecutedRoutedEventArgs e)
{
  object temp;
  if (UndoRedoAction.DoUndo(out temp))
  {
    m_dicUndoRedoAction["DeleteClass"].Invoke(temp);
  }
}

编译器报错

字段初始化程序不能引用非静态字段、方法或属性 'DeleteClassFromeNode'

我还查阅了 Action 的 MSDN 参考文献,但是微软没有提到 Action 是否隐式地是静态的,或者我是否走错了路? 我也查阅了一些从静态方法中调用非静态方法的内容,但没有一个令人满意地解释。 如果有人能提供更低级别的解释,我将不胜感激。

响应 Peter 的解释

尽管初始化程序在构造函数完成之前运行,但这并不会触发构造函数执行期间的委托。即使您在 ILDASM 中查看其程序集代码,它显示实际的 Action Field 是非静态的,但缓存的匿名委托对象是静态的。为什么编译器会有这种不同的行为?

enter image description here


1
private Action<object> DeleteClassFromeNode = ... 看起来有些奇怪。为什么不能像 private void DeleteClassFromeNode(object data) { ... } 一样将其作为一个方法呢?如果你担心的是编译问题,那么 m_dicUndoRedoAction.Add("DeleteClass", DeleteClassFromeNode ); 仍然可以编译通过。 - user743382
嗨hvd,这是因为我通常在我的代码中使用匿名字段。显然,这并不违反csc规则。 - Rohit Prakash
2
你的代码违反了C#语言规则。虽然不太明显,但为了初始化lambda表达式,编译器需要使用this。它被捕获,并且由于DeleteClass()是一个实例方法而需要。而且你不能使用this来初始化字段,因为构造函数尚未完成运行就已经初始化了该字段。你必须将初始化移动到构造函数中。这可能不是你首选的“风格”,但是却是必要的。 - Hans Passant
@hvd 请将其写成答案。这显然是最佳解决方案。对于Rohit Prakash:箭头=>仍将被编译器转换为方法。在您的情况下,最好使用名称DeleteClassFromeNode来命名该方法。当然,命名方法(非静态)的主体可以引用此实例的其他实例成员。正如hvd所说,“方法组”DeleteClassFromeNode隐式转换为类型Action<object>。也许方法参数data的类型应该是一些Tuple<,>而不仅仅是object?请注意,Action<in T>T中是逆变的 - Jeppe Stig Nielsen
@JeppeStigNielsen 谢谢,但我会尽量避免在回答问题时发布与问题无关的内容。Peter Duniho的回答是针对所提出的问题的正确答案,即使我怀疑所提出的问题并不是应该被问到的问题。 - user743382
显示剩余3条评论
1个回答

6
正如编译器所提示的,您不能在初始化程序中使用非静态成员。这是因为初始化程序在构造函数完成之前运行,因此使用非静态成员可能不安全。相反,在构造函数中执行初始化即可:
public MyClass()
{
    DeleteClassFromeNode = data =>
    {
        Tuple<itemType1, itemType2> items = data as Tuple<itemType1, itemType2>;
        if (items != null && items.Item2 != null)
        {
          DeleteClass(items.Item2); // This is my non static method in the same class.
        }
    };

    // Other initialization code can go here (or before...whatever is most appropriate)
}

虽然这样可以工作,但最好将DeleteClassFromeNode变成MyClass的普通(命名的,而不是匿名的)实例方法。所以void DeleteClassFromeNode(object data) { ... }。当然,在该方法的主体内,您可以调用非静态成员DeleteClass。有关更多信息,请参见问题下面hvd的评论。仅在计划稍后使用=,+=-=重新分配字段时,才将其作为委托字段。你打算这样做吗?如果没有,请将其更改为普通方法(或至少声明该字段为readonly)。 - Jeppe Stig Nielsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接