3

在很多情况下,我碰巧遇到这样一种情况,我想知道是否必须在设置变量之前执行 IF 检查(并增加方法的复杂性),或者这是否由 Windows 或框架在内部完成。

例如,假设我们有一个不断被触发的事件,例如 Form 的MouseMove事件。其中哪一种方法会更好用?调用是否this.Cursor = Cursors.SizeNWSE;也在内部检查以确保在不需要时不会执行任何操作,或者它是否盲目地执行代码?

示例 A:

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.X - this.Width > -16 && e.Y - this.Height > -16)
    {
        this.Cursor = Cursors.SizeNWSE;
    }
    else
    {
        this.Cursor = Cursors.Arrow;
    }
}

示例 B:

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.X - this.Width > -16 && e.Y - this.Height > -16)
    {
        if (this.Cursor != Cursors.SizeNWSE)
            this.Cursor = Cursors.SizeNWSE;
    }
    else
    {
        if (this.Cursor != Cursors.Arrow)
            this.Cursor = Cursors.Arrow;
    }
}
4

3 回答 3

2

.NET 框架在设置属性之前不会检查它。Cursor这是用于设置a的反编译代码片段Form

      set
      {
        Cursor cursor1 = (Cursor) this.Properties.GetObject(Control.PropCursor);
        Cursor cursor2 = this.Cursor;
        if (cursor1 != value)
        {
          System.Windows.Forms.IntSecurity.ModifyCursor.Demand();
          this.Properties.SetObject(Control.PropCursor, (object) value);
        }
        if (this.IsHandleCreated)
        {
          System.Windows.Forms.NativeMethods.POINT pt = new System.Windows.Forms.NativeMethods.POINT();
          System.Windows.Forms.NativeMethods.RECT rect = new System.Windows.Forms.NativeMethods.RECT();
          System.Windows.Forms.UnsafeNativeMethods.GetCursorPos(pt);
          System.Windows.Forms.UnsafeNativeMethods.GetWindowRect(new HandleRef((object) this, this.Handle), out rect);
          if (rect.left <= pt.x && pt.x < rect.right && (rect.top <= pt.y && pt.y < rect.bottom) || System.Windows.Forms.UnsafeNativeMethods.GetCapture() == this.Handle)
            this.SendMessage(32, this.Handle, (IntPtr) 1);
        }
        if (cursor2.Equals((object) value))
          return;
        this.OnCursorChanged(EventArgs.Empty);
      }
    }

可以看到每次都会执行一堆代码。检查属性是否已更改,但它只会停止框架运行几行。

于 2013-05-30T17:46:26.987 回答
2

简而言之:是的,进行检查是最有效的。

深入:

我进行了测试和比较(代码在表单的 Load 事件上运行),并且惊讶地发现实际差异。

这是我用来测试的代码:

this.Cursor = Cursors.SizeNWSE;

var sw1 = Stopwatch.StartNew();
for (var i = 0; i < 10000000; i++)
{
    this.Cursor = Cursors.SizeNWSE;
}
sw1.Stop();

var sw2 = Stopwatch.StartNew();
for (var i = 0; i < 10000000; i++)
{
    if (this.Cursor != Cursors.SizeNWSE)
    {
        this.Cursor = Cursors.SizeNWSE;
    }
}
sw2.Stop();

然后我在两者之间进行交换,在第一个案例之前运行第二个案例,只是为了确定。结果是肯定的!

如果:00:00:00.8065328

没有如果:00:00:20.8631726

巨大的不同!不带检查运行比带检查运行慢 20 倍以上。

这意味着在分配游标时,没有检查以确定是否可以忽略分配。

但是,如果分配真的不能被忽略,会发生什么?如果每次都是不同的光标怎么办?

好问题!答案是进一步的测试表明,是检查和非检查版本之间的差异很小,当分配的光标与分配时的实际光标永远不一样时,因此分配永远不会是多余的。不同之处显然在于执行检查所需的额外时间。

ReSharper 可能会警告您检查是多余的(“分配前的冗余检查”),但在这种情况下不是!当然不是。

如果你做了很多,你想检查一下。

在 MouseMove 事件中,我建议进行检查,以防止鼠标滞后。即使您没有注意到它,它也可能显示在速度较慢的机器上。

于 2013-05-30T17:34:08.760 回答
2

不,为什么会这样?.NET 完全没有理由这样做。在分配之前比较某些东西以防止“覆盖”相同的值只会增加复杂性并且可能会降低性能——我们在这里谈论的是微优化。真正的性能微观影响在很大程度上取决于特定目标 CPU 架构的行为、缓存、时序、位置和许多其他因素。

这种检查的唯一原因是如果任何分配都会触发一个动作(在您的示例中Cursor表示一个属性),如果完全相同的值已经存储在特定属性中,这将是不需要的。

因此,如果您需要这种行为,我建议将其封装到属性的设置器中:

public Cursors Cursor
{
    get { return cursor; }
    set
    {
        // prevent reacting to “no change”
        if (cursor != value)
        {
            cursor = value;
            … perform some other action …
        }
    }
}
private Cursors cursor;

总而言之,不要担心性能,担心代码的语义和可读性。


更新:运行另一个答案中提供的测试代码时,我的结果大约在 Core i5 3320M 2.60 GHz CPU 上的00:00:00.0800000 和 00:00:00.1500000 之间。Mycursor被声明为局部变量。需要注意的是,有时无条件版本更快,有时有条件版本更快。

如果在 OP 的情况下,它被声明为一个属性(OP 声明它是一个“变量”),它涉及一些复杂的内部过程,那么是的,带有条件赋值的版本可能会快得多。但是,OPs 的问题并未表明这一点。

在现实世界的软件中,我怀疑会有如此多的光标更改,以至于考虑一秒钟的性能是值得的。

于 2013-05-30T17:37:38.477 回答