【问题标题】:Why can a .NET delegate not be declared static?为什么不能将 .NET 委托声明为静态的?
【发布时间】:2011-10-13 17:26:54
【问题描述】:

当我尝试编译以下内容时:

public static delegate void MoveDelegate (Actor sender, MoveDirection args);

我收到一个错误消息:“修饰符 'static' 对该项目无效。”

我在一个单例中实现这个,有一个单独的类调用委托。问题是,当我在另一个类中使用单例实例来调用委托时(从标识符,而不是类型),无论出于何种原因,我都不能这样做,即使我声明委托是非静态的。显然,只有当且仅当委托是静态的时,我才能通过类型直接引用它。

这背后的原因是什么?我正在使用 MonoDevelop 2.4.2。

更新

在尝试使用以下代码的建议之一后:

public void Move(MoveDirection moveDir)
{
    ProcessMove(moveDir);
}

public void ProcessMove(MoveDirection moveDir)
{
    Teleporter.MoveMethod mm = new Teleporter.MoveMethod(Move); 
    moveDelegate(this, moveDir);
}

我收到一个处理错误,指出 MoveMethod 必须是一个类型,而不是一个标识符。

【问题讨论】:

  • 我认为一个小代码示例将帮助您解释问题。我将第二段读了五遍,但仍然不知道你想要完成什么以及如何完成。
  • ProcessMove方法中mm变量的作用是什么?如果某个委托(静态或实例)分配给moveDelegate,则调用moveDelegate 将调用分配的委托,因为它应该这样做。
  • 我认为接口更适合这个。请看my answer

标签: c# delegates compiler-errors


【解决方案1】:

试试这个:

public delegate void MoveDelegate(object o);
public static MoveDelegate MoveMethod;

所以方法变量可以定义为静态的。关键字staticdelegate 定义没有意义,就像enumconst 定义一样。

如何分配静态方法字段的示例:

public class A
{
  public delegate void MoveDelegate(object o);
  public static MoveDelegate MoveMethod;
}

public class B
{
  public static void MoveIt(object o)
  {
    // Do something
  }    
}

public class C
{
  public void Assign()
  {
    A.MoveMethod = B.MoveIt;
  }

  public void DoSomething()
  {
    if (A.MoveMethod!=null)
      A.MoveMethod(new object()); 
  }
}

【讨论】:

  • 我不太关注。那应该是公共领域还是方法?如果它是一个字段,它是否应该引用一个委托的实例?当我尝试在其他类中引用它时,出现错误:/.
  • 我添加了一个例子。如您所见,静态方法变量就像静态字段一样工作。
  • 声明“就像枚举或常量定义”听起来不正确。 const 在本质上默认是静态的,所以我们不能为 const 类型字段设置静态。给出的例子很好。
  • 嗨 C.Zonnenberg,在 C# 中是否强制要求 B.MoveIt 方法是静态的?为什么?
【解决方案2】:

您正在声明 delegate 类型。将其声明为static 没有任何意义。不过,您可以将delegate 类型的实例声明为static

public delegate void BoringDelegate();


internal class Bar {
    public static BoringDelegate NoOp;
    static Bar() {
        NoOp = () => { };
    }
}

【讨论】:

  • 它可能“有意义”并且实际上非常有用的是能够声明一个永远不能表示实例方法的delegate,即它不会是托管__thiscall,而是一个必要的静态托管方法(在静态或实例类上)。这样的“委托”类型将没有Delegate.Target 属性,并且它的实例没有存储this 对象的规定......也许是现有Delegate 类型的基类?优点是在 IL 中发出 call 而不是 callvirt,这也可以避免堆叠空的 this 值。
【解决方案3】:

委托声明基本上声明了一个方法签名,其中仅包含有关其参数和返回类型的信息。而且由于同一个委托可以同时指向静态方法和实例方法,因此将方法签名本身设为静态或实例是没有意义的。

一旦你将你的委托声明为:

public delegate void MoveDelegate (Actor sender, MoveDirection args);

这意味着该类型的任何委托都必须指向一个方法,该方法接受一个Actor参数,一个MoveDirection参数,并返回void,无论该方法是否为静态或实例。您可以在命名空间范围内或在类内声明委托(就像声明嵌套类一样)。

所以在某处声明MoveDelegate之后,你可以创建该类型的字段和变量:

private MoveDelegate _myMoveDelegate;

并记住该方法应该有一个匹配签名:

// parameters and return type must match!
public void Move(Actor actor, MoveDirection moveDir)
{
    ProcessMove (moveDir);
}

public static void MoveStatic(Actor actor, MoveDirection moveDir)
{
    ProcessMove (moveDir);
}

然后您可以将此方法分配给其他地方的委托:

private void SomeOtherMethod()
{
     // get a reference to the Move method
     _myMoveDelegate = Move;

     // or, alternatively the longer version:
     // _myMoveDelegate = new MoveDelegate(Move);

     // works for static methods too
     _myMoveDelegate = MoveStatic;

     // and then simply call the Move method indirectly
     _myMoveDelegate(someActor, someDirection);
}

知道 .NET(从版本 v3.5 开始)提供了一些预定义的通用委托ActionFunc),可以使用 而不是声明您自己的代表

// you can simply use the Action delegate to declare the
// method which accepts these same parameters
private Action<Actor, MoveDirection> _myMoveDelegate;

恕我直言,使用这些委托更具可读性,因为您可以通过查看委托本身来立即识别参数的签名(而在您的情况下,需要查找声明)。

【讨论】:

  • 您关于委托签名的 cmets 没问题,但与委托 instance 代表静态与实例 方法 的问题无关.这可能是 OP 所追求的……至少这就是我最终出现在此页面上的方式。也许您的回答可能会提到这样一个事实,即相同委托类型的两个实例可以表示表面上具有相同签名的方法,但其中一个是静态的,而另一个是静态的。尽管 C# 努力掩盖它,但这是一个应该理解的显着差异。有关更多动机,请参阅我的其他评论。
  • 我刚刚在msdn.microsoft.com/en-us/library/z84bd3st(v=vs.110).aspx 找到了关于静态与实例委托之间(不洁,恕我直言)关系的讨论。底部有一个特别好的代码示例。
  • @Glenn:也许我应该更新答案,但事实是 .NET 中的委托可以指向实例和静态方法,并在内部存储有关目标的信息。实际上,它有几种不同的存储方式,如“不同类型的代表”下的here 解释。委托的内部类型将决定 JITter 如何在堆栈上排列参数以匹配实际的目标签名,同时允许调用代码透明地将参数传递给委托。
  • 是的,我的观点是,所有这些都是太多的功能变化,无法集中到一个不透明的车辆中。我只是主张更突出地暴露已经存在的区别,并且您实际上证实了(参见“不同类型的代表”,我的观点可能会打趣,“在哪里?!......”)。代表的静态与实例区别,加上适当的管理制度,将提高认识并帮助排除痛苦但常见的编码错误,最糟糕的是当本地无意中落入闭包时静态 lambdas 如何被默默地提升为实例。
  • “知道 .NET(从版本 v3.5 开始)提供了一些预定义的通用委托(Action 和 Func),可以用来代替声明自己的委托”对我(和希望其他人)这是黄金!感谢您提供一个非常好的和明确的答案
【解决方案4】:

委托声明实际上是一个类型声明。它不能是静态的,就像你不能定义静态枚举或结构一样。

但是,我宁愿使用接口而不是原始委托

考虑一下:

public interface IGameStrategy {
    void Move(Actor actor, MoveDirection direction);
}

public class ConsoleGameStrategy : IGameStrategy {
    public void Move(Actor actor, MoveDirection direction)
    {
        // basic console implementation
        Console.WriteLine("{0} moved {1}", actor.Name, direction);
    }
}

public class Actor {
    private IGameStrategy strategy; // hold a reference to strategy

    public string Name { get; set; }    

    public Actor(IGameStrategy strategy)
    {
        this.strategy = strategy;
    }

    public void RunForrestRun()
    {
        // whenever I want to move this actor, I may call strategy.Move() method

        for (int i = 0; i < 10; i++)
            strategy.Move(this, MoveDirection.Forward);
    }
}

在您的调用代码中:

var strategy = new ConsoleGameStrategy();

// when creating Actors, specify the strategy you want to use
var actor = new Actor(strategy) { Name = "Forrest Gump" };
actor.RunForrestRun(); // will write to console

这在精神上类似于Strategy design pattern,并允许您将Actor 移动与实际实施策略(控制台、图形等)分离。以后可能需要其他策略方法,这使其成为比委托更好的选择。

最后,您可以使用Inversion of Control framework 在您的Actor 类中自动注入正确的策略实例,因此无需手动初始化。

【讨论】:

  • 我正在尝试编写一个基于文本的游戏引擎,它允许我创建对象并在地图上移动它们,将它们的对象坐标显示到控制台。这是在添加任何 GUI、图形等之前完成的。我现在决定创建一个传送器类,我制作了一个单例,用于将任何对象移动到特定目的地。现在我保持简单:MoveDirection.Forward 或 MoveDirection.Backward。如果您希望我发布来源,请告诉我。
  • @Holland:我宁愿使用接口(如果我的问题正确的话)。请查看我的编辑。
【解决方案5】:

定义你的委托,在你的静态类中为它声明一个实例变量。

public delegate void MoveDelegate (Actor sender, MoveDirection args);

public static MyClass
{
     public static MoveDelegate MoveDelegateInstance;
}

【讨论】:

    【解决方案6】:
    public static delegate void MoveDelegate (Actor sender, MoveDirection args);
    

    让我告诉你当你声明一个委托时发生了什么

    编译器创建一个类,在本例中名为MoveDelegate,并使用System.MulticastDelegate 对其进行扩展。

    由于您不能通过静态类型扩展任何非静态类型。

    所以这就是编译器不允许静态委托声明的原因。 但是您仍然可以拥有静态委托引用。

    【讨论】:

      猜你喜欢
      • 2011-01-23
      • 2011-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      相关资源
      最近更新 更多