C# Enumerable类 之 生成序列

发布于:2025-02-28 ⋅ 阅读:(10) ⋅ 点赞:(0)

总目录


前言

在 C# 中,System.Linq.Enumerable 类是 LINQ(Language Integrated Query)的核心组成部分,它提供了一系列静态方法,用于操作实现了 IEnumerable 接口的集合。通过这些方法,我们可以轻松地对集合进行查询、转换、排序和聚合等操作。

本文属于 C# Enumerable类 使用详解 中的一个章节,着重介绍 C# Enumerable 类中生成序列这部分的内容。


一、概览

方法 描述 示例
Repeat 生成包含指定元素的重复序列 Enumerable.Repeat("Hello", 5);
Range 生成一系列连续的整数序列 Enumerable.Range(1, 5);

二、Repeat :重复序列

1. 什么是 Repeat

Repeat 是 LINQ 提供的一个静态方法,用于创建一个由指定元素重复构成的序列。它适用于需要生成包含相同元素的可枚举集合的场景。

2. Repeat 方法 基本信息

1) Repeat

Enumerable.Repeat 主要用于生成一个由相同元素组成的序列。它在需要初始化一个包含相同值的集合时非常有用。

public static IEnumerable<TResult> Repeat<TResult>(TResult element, int count);
  • 参数
    • element:要重复的元素。
    • count:要生成的元素数量。
  • 返回值:一个包含指定元素重复指定次数的 IEnumerable<TResult>

2)工作原理

Repeat 方法会生成一个由指定元素重复出现的序列,重复次数由 count 参数指定。如果 count 小于等于0,则返回一个空的序列。

3)使用场景

  • 初始化集合:当需要初始化一个包含相同元素的集合时,可以使用 Repeat 方法。
  • 测试数据生成:在单元测试或其他测试场景中,生成包含相同元素的测试数据。
  • 模拟数据:在开发过程中,模拟一些简单的数据集以验证逻辑的正确性。

3. 使用示例

示例 1:基本用法

生成重复元素的列表

	static void Main()
    {
        IEnumerable<int> repeatedNumbers = Enumerable.Repeat(5, 3);
        foreach (var num in repeatedNumbers)
        {
            Console.WriteLine(num); // 输出: 5 5 5
        }

        List<string> repeatedStrings = Enumerable.Repeat("Hello", 4).ToList();
        foreach (var str in repeatedStrings)
        {
            Console.WriteLine(str); // 输出: Hello Hello Hello Hello
        }
    }

在这个例子中,我们使用 Enumerable.Repeat 创建了一个包含三个 5 的整数序列和一个包含四个 "Hello" 的字符串列表。

示例 2:初始化数组

	static void Main()
    {
        int[] initializedArray = Enumerable.Repeat(-1, 5).ToArray();
        foreach (var num in initializedArray)
        {
            Console.WriteLine(num); // 输出: -1 -1 -1 -1 -1
        }
    }

这里,我们使用 Enumerable.Repeat 初始化一个包含五个 -1 的数组。

示例 3:初始化集合

有时我们需要初始化一个包含相同对象的集合。例如,初始化一个包含多个相同对象的列表。

using System;
using System.Collections.Generic;
using System.Linq;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Name} ({Age})";
    }
}

class Program
{
    static void Main()
    {
        var person = new Person { Name = "Alice", Age = 30 };
        var repeatedPersons = Enumerable.Repeat(person, 3).ToList();

        foreach (var p in repeatedPersons)
        {
            Console.WriteLine(p); // 输出: Alice (30) Alice (30) Alice (30)
        }
    }
}

在这个例子中,我们首先使用 Where 方法筛选出了年龄大于25的人,然后使用 Select 方法只选择了这些人的名字。

示例 4:结合其他 LINQ 方法

Repeat 可以与其他 LINQ 方法结合使用,进一步增强其功能。例如,我们可以先使用 Repeat 方法生成一个序列,然后再使用 Select 方法进行转换。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var repeatedNumbers = Enumerable.Repeat(1, 5); // 生成5个数字1的序列

        var transformedNumbers = repeatedNumbers.Select(n => n * 2); // 将每个数字乘以2

        foreach (var num in transformedNumbers)
        {
            Console.WriteLine(num); // 输出: 2 2 2 2 2
        }
    }
}

在这个例子中,我们首先使用 Repeat 方法生成了一个包含5个数字 1 的序列,然后使用 Select 方法将每个数字乘以2。

示例 5:生成复杂对象序列

我们还可以使用 Repeat 方法生成包含复杂对象的序列,并对其进行操作。例如,生成一个包含多个自定义对象的序列,并对这些对象进行修改。

using System;
using System.Collections.Generic;
using System.Linq;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Name} ({Age})";
    }
}

class Program
{
    static void Main()
    {
        var initialPerson = new Person { Name = "Alice", Age = 30 };
        var persons = Enumerable.Repeat(initialPerson, 3).ToList();

        // 修改第一个对象的属性
        persons[0].Name = "Bob";

        foreach (var p in persons)
        {
            Console.WriteLine(p); // 输出: Bob (30) Bob (30) Bob (30)
        }
    }
}

在这个例子中,我们生成了一个包含3个相同 Person 对象的列表,并修改了第一个对象的 Name 属性。由于所有对象实际上是指向同一个实例的引用,因此所有对象的 Name 都被修改了。

示例 6:生成不同对象的序列

为了避免上述问题(所有对象指向同一个实例),我们可以在 Repeat 方法的基础上使用 Select 方法生成不同的对象实例。

using System;
using System.Collections.Generic;
using System.Linq;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"{Name} ({Age})";
    }
}

class Program
{
    static void Main()
    {
        var persons = Enumerable.Repeat(new { Name = "Alice", Age = 30 }, 3)
                                .Select(a => new Person { Name = a.Name, Age = a.Age })
                                .ToList();

        // 修改第一个对象的属性
        persons[0].Name = "Bob";

        foreach (var p in persons)
        {
            Console.WriteLine(p); // 输出: Bob (30) Alice (30) Alice (30)
        }
    }
}

在这个例子中,我们使用 Repeat 方法生成了一个匿名对象的序列,并通过 Select 方法将其转换为新的 Person 对象实例。这样,每个 Person 对象都是独立的,修改其中一个不会影响其他对象。

4. 注意事项

尽管 Where 方法非常有用,但在实际应用中也有一些需要注意的地方:

  • 引用类型与值类型: 对于引用类型的对象,Repeat 方法生成的序列中的每个元素实际上是同一个对象的引用。这意味着如果修改其中一个对象,所有引用该对象的元素都会受到影响。为了避免这种情况,可以在 Repeat 方法的基础上使用 Select 方法生成新的对象实例。

  • 性能考虑Repeat 方法生成的序列是惰性执行的,只有在实际遍历时才会生成元素。这种设计可以提高性能,特别是在处理大型集合时。

  • 不可变性Repeat 方法不会修改原始元素,而是返回一个新的序列。这意味着原始元素保持不变,新的序列只包含重复的元素。

  • 空集合处理: 如果 count 参数小于等于0,则 Repeat 方法将返回一个空的序列。因此,在使用 Repeat 方法之前,通常不需要检查 count 是否大于0。

三、 Range:连续整数序列

1. 什么是 Range

Range 是 LINQ 提供的一个静态方法,用于生成一个包含从起始值开始的连续整数序列。它适用于需要生成一系列连续数字的场景,如循环控制、数组初始化等。

2. Range方法 基本信息

1)Range

Enumerable.Range 主要用于生成一系列连续的整数。它在需要生成索引、循环计数器或任何其他需要连续整数的场景中非常有用。

public static IEnumerable<int> Range(int start, int count);
  • 参数
    • start:序列中的第一个整数。
    • count:要生成的连续整数的数量。
  • 返回值:一个包含从 start 开始的连续整数的 IEnumerable<int>

2)工作原理

Range 方法会生成一个从 start 开始的整数序列,序列中的元素数量由 count 参数指定。如果 count 小于等于0,则返回一个空的序列。

2)使用场景

  • 循环控制:当需要执行固定次数的循环时,可以使用 Range 方法替代传统的 for 循环。
  • 数组或列表初始化:在初始化数组或列表时,可以使用 Range 方法生成初始值。
  • 测试数据生成:在单元测试或其他测试场景中,生成一系列连续的整数作为测试数据。
  • 模拟数据:在开发过程中,模拟一些简单的数据集以验证逻辑的正确性。

3. 使用示例

示例 1:基本用法

假设我们需要生成一个从1到5的整数序列,我们可以使用 Range 方法。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = Enumerable.Range(1, 5);

        foreach (var num in numbers)
        {
            Console.WriteLine(num); // 输出: 1 2 3 4 5
        }
    }
}

在这个例子中,我们使用 Range 方法生成了一个从1开始的5个连续整数的序列,并遍历输出了这些整数。

示例 2:生成负数序列

我们也可以生成负数序列。例如,生成一个从-5到-1的整数序列。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = Enumerable.Range(-5, 5);

        foreach (var num in numbers)
        {
            Console.WriteLine(num); // 输出: -5 -4 -3 -2 -1
        }
    }
}

在这个例子中,我们生成了一个从-5开始的5个连续整数的序列,并遍历输出了这些整数。

示例 3:结合其他 LINQ 方法

Range 可以与其他 LINQ 方法结合使用,进一步增强其功能。例如,我们可以先使用 Range 方法生成一个序列,然后再使用 Select 方法进行转换。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = Enumerable.Range(1, 5); // 生成1到5的整数序列

        var transformedNumbers = numbers.Select(n => n * 2); // 将每个数字乘以2

        foreach (var num in transformedNumbers)
        {
            Console.WriteLine(num); // 输出: 2 4 6 8 10
        }
    }
}

在这个例子中,我们首先使用 Range 方法生成了一个从1到5的整数序列,然后使用 Select 方法将每个数字乘以2。

示例 4:生成多维数组的索引

有时我们需要生成多维数组的索引。例如,生成一个二维数组的所有索引对。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        int rows = 3;
        int cols = 4;

        var indices = from row in Enumerable.Range(0, rows)
                      from col in Enumerable.Range(0, cols)
                      select new { Row = row, Col = col };

        foreach (var index in indices)
        {
            Console.WriteLine($"({index.Row}, {index.Col})"); // 输出: (0, 0) (0, 1) ... (2, 3)
        }
    }
}

在这个例子中,我们使用 Range 方法生成了一个二维数组的所有索引对,并遍历输出了这些索引。

示例5:初始化集合

有时我们需要初始化一个包含连续整数的集合。例如,初始化一个包含1到10的整数的列表。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = Enumerable.Range(1, 10).ToList(); // 生成1到10的整数序列并转换为列表

        foreach (var num in numbers)
        {
            Console.WriteLine(num); // 输出: 1 2 3 4 5 6 7 8 9 10
        }
    }
}

在这个例子中,我们使用 Range 方法生成了一个从1到10的整数序列,并将其转换为列表。

示例6:生成复杂的对象序列

我们还可以使用 Range 方法生成复杂的对象序列。例如,生成一组自定义对象,每个对象包含一个唯一的ID。

using System;
using System.Collections.Generic;
using System.Linq;

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"Person {Id}: {Name}";
    }
}

class Program
{
    static void Main()
    {
        var personNames = new[] { "Alice", "Bob", "Charlie", "David", "Eve" };
        var people = Enumerable.Range(1, personNames.Length)
                               .Select(i => new Person { Id = i, Name = personNames[i - 1] });

        foreach (var person in people)
        {
            Console.WriteLine(person); // 输出: Person 1: Alice Person 2: Bob ...
        }
    }
}

在这个例子中,我们使用 Range 方法生成了一组自定义 Person 对象,每个对象包含一个唯一的ID和对应的名字。

4. 注意事项

  • 延迟执行Range 方法是惰性执行的,这意味着它不会立即生成整个序列,而是在实际遍历时才生成元素。这种设计可以提高性能,特别是在处理大型序列时。
  • 不可变性Range 方法不会修改原始元素,而是返回一个新的序列。这意味着原始元素保持不变,新的序列只包含生成的整数。
  • 空集合处理:如果 count 参数小于等于0,则 Range 方法将返回一个空的序列。因此,在使用 Range 方法之前,通常不需要检查 count 是否大于0。
  • 溢出问题:如果 start + count - 1 超过了 int.MaxValue,则可能会导致溢出错误。因此,在使用 Range 方法时应确保生成的序列不会超出整数的最大范围。

四、Enumerable.Repeat vs Enumerable.Range


特性 Enumerable.Repeat Enumerable.Range
目标 生成多个相同的元素 生成一系列连续的整数
参数 element: 要重复的元素
count: 元素数量
start: 序列的第一个整数
count: 整数元素数量
返回类型 IEnumerable<TResult> IEnumerable<int>
使用场景 初始化包含相同值的集合 生成索引、循环计数器等

性能对比:生成百万级序列时比 for 循环慢 15%,但代码更简洁


结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
微软官方文档 Enumerable