【问题标题】:Invoke delegates without params but using local params c#在没有参数但使用本地参数 c# 的情况下调用委托
【发布时间】:2010-02-25 02:17:52
【问题描述】:

我发现自己经常执行以下操作,我不知道是否有任何副作用,但请考虑在 WinForms C# 应用程序中执行以下操作。 (请原谅我在输入代码时出现的任何错误,而不是复制粘贴任何内容)

int a = 1;
int b = 2;
int c = 3;
this.Invoke((MethodInvoker)delegate()
{
    int lol = a + b + c;
});

这有什么问题吗?还是我应该走很长的路>_

int a = 1;
int b = 2;
int c = 3;
TrippleIntDelegate ffs = new TrippleIntDelegate(delegate(int a_, int b_, int c_)
{
   int lol = a_ + b_ + c_;
});

this.Invoke(ffs);

不同之处在于参数是传入而不是使用局部变量,这是一些非常甜蜜的 .net 魔法。我想我曾经看过反射器,它创建了一个全新的类来保存这些变量。

那么重要吗?我可以偷懒吗?

编辑:注意,显然不要关心返回值。否则我将不得不使用我自己的类型化委托,尽管我仍然可以使用局部变量而不传递它!

【问题讨论】:

    标签: c# parameters delegates lazy-evaluation


    【解决方案1】:

    您使用它的方式并没有真正的不同。但是,在第一种情况下,您的匿名方法正在捕获变量,如果您不知道自己在做什么,这可能会产生很大的副作用。例如:

    // No capture :
    int a = 1;
    Action<int> action = delegate(int a)
    {
        a = 42; // method parameter a
    });
    action(a);
    Console.WriteLine(a); // 1
    
    // Capture of local variable a :
    int a = 1;
    Action action = delegate()
    {
        a = 42; // captured local variable a
    };
    action();
    Console.WriteLine(a); // 42
    

    【讨论】:

      【解决方案2】:

      只要您了解您正在延迟执行,传入局部变量就没有错。如果你这样写:

      int a = 1;
      int b = 2;
      int c = 3;
      Action action = () => Console.WriteLine(a + b + c);
      c = 10;
      action();  // Or Invoke(action), etc.
      

      这个输出将是 13,而不是 6。我想这将与 Thomas 所说的对应;如果您读取委托中的局部变量,它将使用实际执行动作时变量所持有的任何值,而不是声明时。如果变量包含引用类型并且您异步调用委托,这可能会产生一些有趣的结果。

      除此之外,还有很多充分的理由将局部变量传递给委托;除其他外,它还可用于简化线程代码。只要你不马虎就可以了。

      【讨论】:

      • 是的,这正是我想要的
      【解决方案3】:

      好吧,所有其他答案似乎都忽略了多线程上下文以及在这种情况下出现的问题。如果您确实在 WinForms 中使用它,那么您的第一个示例可能会引发异常。根据您尝试从委托中引用的实际数据,实际调用代码的线程可能有权访问您关闭的数据,也可能无权访问。

      另一方面,您的第二个示例实际上是通过参数传递数据。这允许 Invoke 方法跨线程边界正确编组数据并避免那些讨厌的线程问题。如果您从后台工作人员调用 Invoke,那么您应该使用类似于第二个示例的内容(尽管我会选择使用 Action 和 Func 代表可能而不是创造新的)。

      【讨论】:

      • 实际上,将局部变量传递给委托可以消除某些多线程问题,只要一切都在范围内。一个这样的示例是使用ThreadPool 并行化任务,在完成时将ManualResetEvent 传递给每个任务以设置,然后等待所有等待句柄。比替代方案更容易(无论如何在 .NET 3.5 中)。这样的线程没有问题,虽然如果你启动一个线程然后让本地人在线程完成之前离开范围显然会成为一个问题(正如我之前提到的)。
      • 我认为关闭当地人是一个滑坡。我可能会想到一个相反的场景,但是当涉及到 WinForms 和控件时,控件及其子项和属性只能由 UI 线程访问。试图从另一个线程访问它们的任何部分往往会导致异常。我相信 WPF 和其他基于 XAML 的东西也有类似的问题。如果您消除 UI,那么我认为没有任何问题......但是当您确实拥有 UI 时,需要注意这一点。
      【解决方案4】:

      从样式的角度来看,我会选择参数传递变体。它表达了更容易传递 args 而不是任何类型的 take 环境的意图(并且也更容易测试)。我的意思是,你可以这样做:

      public void Int32 Add()
      {
          return this.Number1 + this.Number2
      }
      

      但它既不可测试也不清晰。带参数的 sig 对其他人来说更清楚该方法在做什么......它添加了两个数字:不是任意一组数字或其他任何东西。

      我经常使用像集合这样的参数来执行此操作,这些参数无论如何都通过 ref 使用,并且不需要明确地“返回”:

      public List<string> AddNames(List<String> names)
      {
          names.Add("kevin");
          return names;
      }
      

      尽管名称集合是由 ref 传递的,因此不需要显式返回,但对我来说更清楚的是,该方法获取列表并将其添加到其中,然后将其返回。在这种情况下,没有以这种方式编写 sig 的技术原因,但对我来说,就清晰度和可维护性而言,这是一个很好的理由。

      【讨论】:

      • 嗯,我说的更多的是委托而不是方法,那是另一回事
      • 是的。我知道。我的观点是为了简洁而不是简洁。对委托和方法都是如此。
      猜你喜欢
      • 2023-03-10
      • 1970-01-01
      • 2012-05-03
      • 1970-01-01
      • 1970-01-01
      • 2015-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多