[英]Enumerable.Range - When does it make sense to use?
在編程時,決定何時使用 for 循環或 foreach 幾乎是本能的,但是選擇使用 Enumerable.Range 的決定因素或問題空間是什么?
當我們想要迭代一定次數(通過簡單數據類型)來計算/執行重復性任務時,會選擇For 循環。
For Each是類似的,但是當我們想要迭代復雜對象列表以計算/執行重復性任務時選擇。
同樣,使用 Enumerable.Range 的決定因素是什么?
IEnumerable<int> squares = Enumerable.Range(4, 3).Select(x => x * x);
foreach
是關於迭代現有的集合/集合。
Enumerable.Range
用於生成集合/集合。 如果可以由Enumerable.Range
生成,您通常不會想要編寫一個for
循環來生成一個集合 - 您只會編寫更長的樣板代碼並且需要您分配某種存儲(例如List<int>
) 首先填充。
如前所述, Enumerable.Range 不是針對循環,而是創建范圍。 這使得 Linq 中的一個襯墊成為可能,而無需創建子集。 這種功能的另一個優點是,您甚至可以在 sub 語句中生成一個子范圍,這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>>
(每所房子的房間字符串可枚舉)。
林克:
listrooms = houses.Select(h => Enumerable.Range(1, h.Rooms).Select(i => h.Name + ", room " + i));
但是現在,我們在使用迭代時需要 2 個集合:
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);
}
另一種情況,我認為 Linqs 和 lambdas 的一大優點是您可以將它們用作參數(例如用於注入目的),這可以通過 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 可以節省一天。
Enumerable.Range()
是一個生成器,即它是一種生成某種類型的n
項的簡單而強大的方法。
需要一個包含某個類的隨機數量實例的集合? 沒問題:
Enumerable.Range(1,_random.Next())
.Select(_ => new SomeClass
{
// Properties
});
您可以將Enumerable.Range
視為檢索集合子集的工具。
如果集合的內容是值類型,則還為它們中的每一個創建一個新實例。
是的,您可以使用for
循環,通過設置正確的迭代邊界來實現,但是這樣想,幾乎所有LINQ
都可以通過其他方式實現(最終它只是一個庫)。
但它具有簡短、簡潔和清晰的語法,這就是它被如此廣泛使用的原因。
你的例子已經足夠好了。
我可以用 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
是一個生成器,它像所有其他 Linq 方法一樣返回一個IEnumerable
並且只在迭代時創建事物。 所以永遠不存在包含所有數字的集合。
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.