Enumerable.Range - 何时使用它才有意义?

28

在编程中,使用for循环或foreach循环几乎是本能的选择,但是在什么情况下会选择使用Enumerable.Range呢?

当我们想要迭代一组已知次数的数据类型以计算/执行重复任务时,选择for循环

当我们想要迭代一组复杂对象列表以计算/执行重复任务时,选择foreach循环

那么,使用Enumerable.Range的决定因素是什么呢?

IEnumerable<int> squares = Enumerable.Range(4, 3).Select(x => x * x);

2
生成单元测试数据? - Gusdor
3
你的方块示例已经展示了这一点,你不能在 LINQ 表达式中使用 for 循环(除非它是用来首先填充集合的)。因此,我认为这绝对是使用 Enumerable.Range 的情况。 - Me.Name
@DaneBalia:我认为明确的陈述是:“使用任何使你的代码更易于阅读的东西。” 你的方块示例(几乎)字面上读作:“我想要4-6范围内的x * x……”这将不会比这更好,因此在这里使用Enumerable.Range是正确的选择。 - Heinzi
2
@DaneBalia:使用LINQ可以做的所有事情,你也可以用循环来完成(通常LINQ方法就是这样实现的)。它们是完成相同任务的两种不同方式,一种更“函数式”(LINQ),另一种更“命令式”。因此,Enumerable.Range是使LINQ库完整的必要条件(否则你如何实现for(int i ...)循环呢?) - digEmAll
@DaneBalia 你说得对,可以使用for/foreach来完成,正如其他人提到的那样,实际上所有的linq都可以用其他方式完成,我认为没有一个黄金法则告诉你何时必须使用它,但我已经添加了一个答案,其中包含一些提示你更喜欢Enumerable.Range的场景。 - Me.Name
显示剩余2条评论
6个回答

32

foreach 是用于在现有的组合/集合上进行迭代

Enumerable.Range 用于生成一个集合。如果可以通过Enumerable.Range来生成一个集合,通常不需要编写 for 循环- 这只会让你编写更长的样板代码,并需要先分配一些存储(例如 List<int>)进行填充。


13

正如提到的那样,Enumerable.Range并不是专门用于循环,而是用于创建范围。这使得在Linq中可以轻松完成一行代码,而无需创建子集。

另一个优点是,您甚至可以在子语句中生成子范围,这在使用for和lambda时并不总是可能,因为在lambda内部不允许使用yield语句。

例如,SelectMany也可以使用Enumerable.Range。

测试集合:

class House
{
    public string Name { get; set; }
    public int Rooms;
}

    var houses = new List<House>
    {
        new House{Name = "Condo", Rooms = 3},
        new House{Name = "Villa", Rooms = 10}
    };

这个例子本身并没有太大的价值,但是为了获取所有房间,可以使用 Linq 实现:

   var roomsLinq = houses.SelectMany(h => Enumerable.Range(1, h.Rooms).Select(i => h.Name + ", room " + i));

通过迭代,需要进行2次迭代:

   var roomsIterate = new List<string>();
    foreach (var h in houses)
    {
        for (int i = 1; i < h.Rooms + 1; i++)
        {
            roomsIterate.Add(h.Name + ", room " + i);
        }
    }

您仍然可以说,第二段代码更易读,但这归结于是否通常使用 Linq。


因此,更进一步,我们想要一个包含所有房间的 IEnumerable<IEnumerable<string>>(每个房屋的字符串枚举房间)。

Linq:

listrooms = houses.Select(h => Enumerable.Range(1, h.Rooms).Select(i => h.Name + ", room " + i));

但是现在,在迭代时我们需要使用两个集合:

    var list = new List<IEnumerable<string>>();
    foreach (var h in houses)
    {
        var rooms = new List<string>();
        for (int i = 1; i < h.Rooms + 1; i++)
        {
            rooms.Add(h.Name + ", room " + i);
        }
        list.Add(rooms);
    }

另一个场景,我认为 Linq 和 lambda 的伟大之处之一在于,您可以将它们用作参数(例如,用于注入目的),这在使用 Enumerable.Range 更容易实现。

例如,您有一个函数,需要一个名为 roomgenerator 的参数。

static IEnumerable<Furniture> CreateFurniture(Func<House,IEnumerable<string>> roomgenerator){
   //some house fetching code on which the roomgenerator is used, but only the first 4 rooms are used, so not the entire collection is used.
}

上面的房间迭代可以使用Enumerable.Range返回,但是要进行迭代,必须首先创建一个子集合来存放房间,或者创建一个单独的函数来产生结果。 子集合的缺点在于它总是完全填充,即使只需要枚举中的一个项目。单独的方法通常是不必要的,因为它仅用于单个参数的使用,因此Enumerable.Range可以解决问题。


1
刚想到另一种情况:您可以在 Enumerable.Range 上使用 AsParallel :) - Me.Name

6

Enumerable.Range()是一个生成器,也就是它是一种简单而强大的方式来生成某种类型的n个项。

需要一个包含某个类的随机数量实例的集合吗?没问题:

Enumerable.Range(1,_random.Next())
    .Select(_ => new SomeClass
    {
        // Properties
    });

3

你的示例已经足够好了。

我可以使用for循环编写相同的代码来构建结果

基本上有两种方法可以使用循环来构建它:

// building a collection for the numbers first
List<int> numbers = new List();
for (int i = 4; i < 7; i++)
    numbers.Add(i);

IEnumerable<int> squares = numbers.Select(x => x * x);

// or building the result directly
List<int> squares = new List();
for (int i = 4; i < 7; i++)
    numbers.Add(i * i);

使用这两种解决方案的共同之处在于实际上需要创建一个集合并将其添加到其中。因此,你需要在某个地方拥有一组数字。
但是看看`Enumerable.Range`的解决方案:Enumerable.Range是一个生成器,它返回一个`IEnumerable`,就像所有其他Linq方法一样,并且只有在迭代时才会创建这些内容。因此,永远不存在包含所有数字的集合。

2
您可以将Enumerable.Range视为检索集合子集的工具。
如果集合的内容是值类型,您还将创建每个内容的新实例。
是的,您可以使用for循环来做到这一点,通过正确设置迭代范围,但是以这种方式思考,几乎所有的LINQ都可以用其他方式实现(最终它只是一个库)。
但是它具有简短,简洁和清晰的语法,这就是为什么它被如此广泛使用的原因。

1

答案

for更快。

Enumerable.Range更易读/更干净。

如果你处理的是大型数组,请使用for循环。如果你处理的是较小的输入,则更喜欢使用Enumerable.Range——选择你的权衡。

性能测试

public static void Main(string[] args)
{
    var stopwatch = new Stopwatch();
    var size = 1000000;
    int[] arrRange;
    
    stopwatch.Start();
    arrRange = Enumerable.Range(0, size).ToArray();
    stopwatch.Stop();
    P(stopwatch, "Enumerable.Range");

    stopwatch.Reset();
    var arrFor = new int[size];
    stopwatch.Start();
    for (var i = 0; i < size; i++)
        arrFor[i] = i;
    stopwatch.Stop();
    P(stopwatch, "For");

    var arrWhile = new int[size];
    var iw = 0;
    stopwatch.Reset();
    stopwatch.Start();
    while (iw < size)
    {
        arrWhile[iw] = iw;
        iw++;
    }
    stopwatch.Stop();
    P(stopwatch, "while");

    stopwatch.Reset();
    stopwatch.Start();
    var arrRepeat = Enumerable.Repeat(1, size);
    stopwatch.Stop();
    P(stopwatch, "Enumerable.Repeat");
}

public static void P(Stopwatch s, string type)
{
    Console.WriteLine($"{type}: {s.Elapsed}");
}

这是该程序的输出结果(在.NET 6.0中)。
Enumerable.Range: 00:00:00.0070907
For: 00:00:00.0036731
while: 00:00:00.0036175
Enumerable.Repeat: 00:00:00.0003927

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接