65.9K
CodeProject 正在变化。 阅读更多。
Home

滞后泛型:轻松约束变量的变化率

2007年12月26日

CPOL

6分钟阅读

viewsIcon

19872

downloadIcon

152

本文展示了一个使用泛型创建变量的非常基本的示例,这些变量的变化率不能超过特定速率。

Screenshot - Article_source

引言

泛型是 .NET 和 C# 环境的一大改进,它随着该平台的 2.0 版本一起出现。本质上,泛型允许在客户端代码中声明和实例化类或方法时推迟类型的指定。稍后,您将找到更深入、更易于理解的解释。

本文展示了一个非常基础的泛型类编程示例,用于执行一个非常具体的功能:限制变量的更改频率,无论其类型是什么。

如果您处理实时应用程序(例如游戏、模拟器或任何其他快速且并非完全由事件驱动的应用程序),您会发现此功能非常有用。

关于泛型

如果您对它们是什么(毕竟,这是一篇初学者文章)一无所知,那么这里有一个更简单的解释。

有些操作必须适用于任何类型的对象。例如:集合。我们是否必须为整数编写一个集合类,为浮点数编写一个不同的集合类,为我们自己的客户编写另一个集合类?当然不用,那太疯狂了!

那么我们能做什么呢?直到 .NET 1.1,解决方案是构建一个处理 objects 的集合类。由于 .NET 中的一切都继承自 object 类,因此一切都可以放入这种集合中。当我们需要包含不同元素的集合时,这仍然非常有用:第一个是整数,第二个是客户,等等。

但事实是,在许多情况下,我们不需要在同一个集合中包含不同的对象。那么,如果对象都是相同的,这种解决方案优雅吗?实际上不是。

当我们使用 object 作为基类型时,每次访问或插入元素到集合时,都必须将其转换为 object 类型(装箱)并从中转换回来(拆箱)。这被称为装箱拆箱,除了额外的代码、工作和一些舒适度损失外,还会带来性能损失。

自 .NET 2.0 以来,有一个更好的解决方案:泛型

基本上,遵循集合的例子,泛型允许您使用一个未指定的类型来编写您的集合类,目前将该类型称为 T。要将您的类声明为泛型,您的代码必须如下所示:

public class List<T> { ... } 

正如您在下面的示例中看到的,此泛型类型 T 可以在您的类中像真实类型一样使用:例如 floatint,任何类型...

public void Add(T pElement)
{
...
}

当您的类完成并且项目的其他部分或其他人使用它时,基对象的类型将必须被指定。例如,如果您需要一个客户集合,您可以这样声明和实例化它:

MyCollections.List<MyCustomers> mList = new MyCollections.List<MyCustomers>();

这会创建一个只能使用 MyCustomer 类型对象的列表,这一点很棒,因为 Visual Studio 会自动在任何地方识别它,并且每个参数、每个方法、一切都将与 MyCustomer 一起工作。这样,您就可以强类型化您的方法和类,减少错误并加快编码速度,这要归功于智能感知。

正如您可以想象的那样,集合是泛型最明显和最直接的应用,并且它们已在 .NET Framework 2.0 的 System.Collections.Generic 命名空间中实现。

总之,泛型可以做很多不同的事情,而本文就展示了其中一种。

在这个例子中,我们将如何使用泛型?

正如我们之前所说,在像游戏这样的实时应用程序中工作时,并非一切都由事件驱动。代码中有一些部分必须以机器所能达到的最快速度执行,这通常促使程序员使用主循环,在其中解析应用程序的全部逻辑。让我向您展示一个这个问题的简单示例。

设想一个这样的应用程序。它不是事件驱动的,而是有一个主循环,其中解析了所有游戏逻辑。设想我们有一个标志,指示我们是否要渲染选项菜单,例如。什么会激活和禁用选项菜单?让我们举个例子,比如一个特定的按键。

现在设想,出于性能原因,我们正在通过 DirectX 读取键盘,DirectX 实际上直接访问键盘硬件,绕过了操作系统。现在,如果用户按下该特定按键,尝试打开或关闭菜单,应用程序实际上会在按键按下期间循环很多次,并且也会多次报告。如果您不在其他地方避免这种情况,其效果是菜单会有点闪烁,有时最终会显示,有时最终会被隐藏,所有这些都只是一次按键操作。

显然,在许多情况下也会发生类似的情况,并且可以以多种不同的方式解决,也许是更简单的解决方案。无论如何,我认为包含某种计时来控制变量最大更改速率会很有趣。老实说,我没有对此问题进行任何研究,而且它可能已经在别处完成,但我认为这对初学者来说是一个很好的例子,所以我们开始吧!

延迟泛型

正如我们所说,我们的挑战是控制变量的最大更改速率。什么类型的变量?我猜是任何类型的...嗯...这听起来很熟悉。我听到“泛型”了吗?是的,我们有一个操作必须与变量类型无关。

因此,您将在源代码中找到 LaggedGeneric 类的实现,该类基本上就是这样做的:它限制了变量值可以更改的最大速率。

首先,我们显然需要声明新类,它可能会有一些 private 成员来控制最大速率频率。我们将这个速率表示为更改之间的延迟,以秒为单位。

  public class LaggedGeneric<T>
  {
        // Specify change frequency
        private double mLagSecs;
        private double mLastSetSeconds = 0;
        public double LagSecs
        {
            get { return mLagSecs; }
            set { mLagSecs = value; }
        }

        // Class´ constructor
        public LaggedGeneric(double pLagSecs)
        {
            this.mLagSecs = pLagSecs;
        }

我们还将提供一个事件来报告值更改。就像这样:

   // Events
   public delegate void ValueChangedDelegate(
                  LaggedGeneric pVar, T pNewValue, T pOldValue);
   public event ValueChangedDelegate ValueChanged = null;

现在,我们必须将值存储在某个地方。请记住,我们正在使用泛型。我们不知道要存储的数据类型,因此我们将变量 mValue 声明为 T 类型。我们还提供了一个普通的访问属性,允许类用户直接更改值(没有计时控制或约束)。每次值更改时,都会触发 ValueChanged 事件。

 private T mValue;
 // Normal access property. To change the value whenever you want
 public T Value
 {
    get { return mValue; }
    set
    {
       T old = mValue;
       mValue = value;
       if (!old.Equals(mValue) && ValueChanged != null)
           ValueChanged(this, mValue, old);
    }
 }

最后,我们提供一个名为 LaggedValue 的附加访问属性。此属性将实现所有计时控制。

  // Lagged access property 
  public T LaggedValue
  {
      get { return this.mValue; }
      set
      {
          double difSecs = System.DateTime.Now.TimeOfDay.TotalSeconds - 
                           mLastSetSeconds;
          if (difSecs >= mLagSecs)
          {
              mLastSetSeconds = 
              System.DateTime.Now.TimeOfDay.TotalSeconds;
                   
              T old = mValue;
              mValue = value;

              if (!old.Equals(mValue) && ValueChanged != null)
                  ValueChanged(this, mValue, old);
          }
      }
  }
}  // end of class

Using the Code

在本文附带的源代码中,您将找到一个功能齐全的 Visual Studio 2005 Windows Forms 项目。它非常简单,只是显示了一个窗口,允许您更改 Lagged boolean 标志的最大更改速率。应用程序将在每次蓝色的面板的 OnMouseMove 事件(鼠标光标进入并围绕其移动时触发)中尝试更改其值。如果您一直移动鼠标,您将看到标志值以您在 NumericUpDown 中指定的速率(或多或少)精确地更改。

如果您想在您的项目中使用 LaggedGeneric 类,只需将文件复制/粘贴到您的解决方案中,然后像以下示例一样使用它:

声明一个延迟的 boolean 或标志(最大更改速率 = 0.5 秒)

LaggedGeneric<bool> mLaggedBoolean = new LaggedGeneric<bool>(0.5);

值切换

mLaggedBollean.LaggedValue = !mLaggedBollean.LaggedValue;

当然,这可以扩展到您想要使用的任何类型的对象。希望您觉得有用。

历史

  • 2007年12月26日:初版
© . All rights reserved.