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






4.44/5 (6投票s)
本文展示了一个使用泛型创建变量的非常基本的示例,这些变量的变化率不能超过特定速率。

引言
泛型是 .NET 和 C# 环境的一大改进,它随着该平台的 2.0 版本一起出现。本质上,泛型允许在客户端代码中声明和实例化类或方法时推迟类型的指定。稍后,您将找到更深入、更易于理解的解释。
本文展示了一个非常基础的泛型类编程示例,用于执行一个非常具体的功能:限制变量的更改频率,无论其类型是什么。
如果您处理实时应用程序(例如游戏、模拟器或任何其他快速且并非完全由事件驱动的应用程序),您会发现此功能非常有用。
关于泛型
如果您对它们是什么(毕竟,这是一篇初学者文章)一无所知,那么这里有一个更简单的解释。
有些操作必须适用于任何类型的对象。例如:集合。我们是否必须为整数编写一个集合类,为浮点数编写一个不同的集合类,为我们自己的客户编写另一个集合类?当然不用,那太疯狂了!
那么我们能做什么呢?直到 .NET 1.1,解决方案是构建一个处理 objects
的集合类。由于 .NET 中的一切都继承自 object
类,因此一切都可以放入这种集合中。当我们需要包含不同元素的集合时,这仍然非常有用:第一个是整数,第二个是客户,等等。
但事实是,在许多情况下,我们不需要在同一个集合中包含不同的对象。那么,如果对象都是相同的,这种解决方案优雅吗?实际上不是。
当我们使用 object
作为基类型时,每次访问或插入元素到集合时,都必须将其转换为 object
类型(装箱)并从中转换回来(拆箱)。这被称为装箱和拆箱,除了额外的代码、工作和一些舒适度损失外,还会带来性能损失。
自 .NET 2.0 以来,有一个更好的解决方案:泛型。
基本上,遵循集合的例子,泛型允许您使用一个未指定的类型来编写您的集合类,目前将该类型称为 T
。要将您的类声明为泛型,您的代码必须如下所示:
public class List<T> { ... }
正如您在下面的示例中看到的,此泛型类型 T
可以在您的类中像真实类型一样使用:例如 float
、int
,任何类型...
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日:初版