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

使用 Yield Return 的高效通用递归迭代器

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (19投票s)

2007年6月1日

CPOL

3分钟阅读

viewsIcon

54620

本文演示了如何使用泛型和 yield return 来优化递归迭代容器控件的常见编程任务。

引言

开发人员经常执行的操作是递归地遍历控件容器(如 FormPanel)中的所有控件及其所有子容器。

我在这里列出的代码是一种简单但非常有效的通用方法。 这绝不是一个震惊世界的革命性突破,但它的纯粹简单性很好!

任务

假设发生了无效命令,您希望将表单上的所有 TextBox 设置为显示 "n/a"。 因此,您将不得不递归地查找此 Form 容器及其可能包含的所有其他子容器中的所有 TextBox

一种效率低下但经常使用的方法

一种常用的方法是创建一个单独的例程,该例程返回一个 TextBox 控件列表,然后可以在调用例程中将其设置为 "n/a"。 像这样...

public static List<Control> GetControlsOfType(Control ctrMain)
{
    List<Control> list = new List<Control>();

    foreach (Control c in ctrMain.Controls)
    {
        if (c.GetType().Equals(typeof(TextBox)))
            list.Add(c);

        if (c.Controls.Count > 0)
            list.AddRange(GetControlsOfType(c));
    }

    return list;
}

... 像这样使用

private void button1_Click(object sender, EventArgs e)
{
    foreach (Control c in Utils.GetControlsOfType(this))
        c.Text = "n/a";
}

这种技术有效,但有一些丑陋的缺点

  • 如果需要查找 Form 中的所有 Button 而不是 TextBox 怎么办? 您将不得不复制您的代码或在其中放置一个 switch 来区分控件类型。
  • 您正在创建一个新的 List,该列表可能仅使用一次,之后它将被指定用于垃圾回收。 如果您经常这样做,并且可能涉及更多项目,那么这可能会对您的内存占用和代码效率产生真正的影响。 请记住,毕竟您正在创建并填充一个包含所有项目的 List,只是为了设置一个 Text 属性,之后您不再需要此长列表并将其dispose 掉!!

通用 IEnumerable 方法

现在看看下面的代码。 通过使用泛型,我们解决了绑定到特定 Type 的第一个问题。 现在,在调用该方法时,我们必须通过指定 <T> 来指定我们要搜索的 Type

然后,通过使用 yield return 语句并返回通用 IEnumerable<T> 而不是完全填充的临时 List,我们能够显着减少内存占用并使代码更高效!!

public delegate bool delTypeCheck(Type type);

/// <summary>
/// Examines the full content of this control's Controls 
/// collection and returns the enumerator instead of a generic list.
/// </summary>
/// <typeparam name="T">The Type we are looking for - 
/// we will also treat any derived types as a match!</typeparam>
/// <param name="ctrMain">The control to examine.</param>
/// <param name="IsExemptCheck">A delegate allowing us to specify 
/// which controls to exempt from examining.</param>
/// <returns>The actual enumerator allowing us to NOT have 
/// to create a helper intermediate list.</returns>
public static IEnumerable<T> GetControlsOfType<T>
	(Control ctrMain, delTypeCheck IsExemptCheck) where T : class
{
    // Determine the Type we need to look out for
    Type searchType = typeof(T);

    foreach (Control c in ctrMain.Controls)
    {
        // If user wants to exclude certain types then a call-back has been given
        // So call it with the current control-type and check if it is exempt.
        if (IsExemptCheck != null && IsExemptCheck(c.GetType()))
            continue;   // the type of c is exempt so continue.

        // If a match is found then yield this item back directly    
        if (c is T) yield return (c as T);
            
        // if you want to search for specific Types only (and NOT derived types) then
        // uncomment the following lines (and comment out the above statement).
        //if (c.GetType().Equals(searchType))
        //    yield return (c as T);

        // If the control hosts other controls then recursively call this function again.
        if (c.Controls.Count > 0)
            foreach (T t in GetControlsOfType<T>(c, IsExemptCheck))
                yield return t;
    }
}

它是这样使用的。

如果我们要找到所有派生自 TextBoxBase 的控件(包括 TextBoxRichTextBox)并将所有这些控件的 Text 属性设置为 "n/a",那么请实现如下所示的代码

foreach (Control c in Utils.GetControlsOfType<TextBoxBase>(this, null))
{
     c.Text = "n/a";
}

此外,排除某些类型进行检查。

假设您要排除搜索某些类型的容器(在本例中为 Panel),那么您可以提供一个匿名 delegate 来排除这些容器(请参见下面的代码)。 'IsExemptCheck' delegate 传递正在检查的 Type,并要求您返回是否应该排除它。 这为调用者提供了更大的范围,也可以指向一个更复杂的功能来确定是否检查控件。 在这种情况下,我们只想排除 Panel 类型,因此一个简单的匿名 delegate 就足够了。

foreach (Control c in Utils.GetControlsOfType<TextBoxBase>
	(this, delegate(Type MyType) { return MyType.Equals(typeof(Panel)); }))
{
     c.Text = "n/a";
}

请注意:不要忘记约束 'where T : class'。 我们将其放在上面是为了确保我们仅处理引用类型。 否则,语句 'yield return (c as T)' 将失败,因为将控件 c 强制转换为类型 T 显然只能与 Control 类型(引用类型)一起发生!

Just a Word (随便说几句)

通过这个例子,我只是想传达一种不同的看待解决此类任务的方式。

Form 中使用它来查找 TextBox,您可能不会直接注意到速度或内存的改善,但请再次尝试将其用于包含成千上万个条目的 List(s) 迭代,您很快就会发现差异! :)

历史

  • 2007 年 6 月 1 日:文章发布
  • 2007 年 6 月 6 日:文章更新 - 语句更改为 (c is T) - 感谢 PIEBALDconsult!
© . All rights reserved.