/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Emit/CodeGen/ForeachTest.cs
1 695 строк
41 KB
Julien Couvreur
Centralize recognized feature flags (#81591)
11 дек 2025, 01:34
Не верифицирован
11 дек 2025, 01:34
7da6b9c
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UnitTests.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { public class ForeachTest : EmitMetadataTestBase { // The loop object must be an array or an object collection [Fact] public void SimpleLoop() { var text = @" using System; public class Test { static void Main(string[] args) { string[] arr = new string[4]; // Initialize arr[0] = ""one""; // Element 1 arr[1] = ""two""; // Element 2 arr[2] = ""three""; // Element 3 arr[3] = ""four""; // Element 4 foreach (string s in arr) { System.Console.WriteLine(s); } } } "; string expectedOutput = @"one two three four"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestIteration() { CompileAndVerify(@" using System; public class Test { public static void Main(string[] args) { unsafe { int* y = null; foreach (var x in new int*[] { y }) { } } } }", options: TestOptions.UnsafeReleaseDll, verify: Verification.Fails).VerifyIL("Test.Main", @" { // Code size 33 (0x21) .maxstack 4 .locals init (int* V_0, //y int*[] V_1, int V_2) IL_0000: ldc.i4.0 IL_0001: conv.u IL_0002: stloc.0 IL_0003: ldc.i4.1 IL_0004: newarr ""int*"" IL_0009: dup IL_000a: ldc.i4.0 IL_000b: ldloc.0 IL_000c: stelem.i IL_000d: stloc.1 IL_000e: ldc.i4.0 IL_000f: stloc.2 IL_0010: br.s IL_001a IL_0012: ldloc.1 IL_0013: ldloc.2 IL_0014: ldelem.i IL_0015: pop IL_0016: ldloc.2 IL_0017: ldc.i4.1 IL_0018: add IL_0019: stloc.2 IL_001a: ldloc.2 IL_001b: ldloc.1 IL_001c: ldlen IL_001d: conv.i4 IL_001e: blt.s IL_0012 IL_0020: ret }"); } // Using the Linq as iteration variable [Fact] public void TestLinqInForeach() { var text = @"using System; using System.Linq; public class Test { public static void Main(string[] args) { foreach (int x in from char c in ""abc"" select c) { Console.WriteLine(x); } } }"; string expectedOutput = @"97 98 99"; CompileAndVerify(text, expectedOutput: expectedOutput); } // Empty foreach statement [Fact] public void TestEmptyStatementForeach() { var text = @"class C { static void Main() { foreach (char C in ""abc""); } }"; string expectedIL = @"{ // Code size 32 (0x20) .maxstack 2 .locals init (string V_0, int V_1) IL_0000: ldstr ""abc"" IL_0005: stloc.0 IL_0006: ldc.i4.0 IL_0007: stloc.1 IL_0008: br.s IL_0016 IL_000a: ldloc.0 IL_000b: ldloc.1 IL_000c: callvirt ""char string.this[int].get"" IL_0011: pop IL_0012: ldloc.1 IL_0013: ldc.i4.1 IL_0014: add IL_0015: stloc.1 IL_0016: ldloc.1 IL_0017: ldloc.0 IL_0018: callvirt ""int string.Length.get"" IL_001d: blt.s IL_000a IL_001f: ret }"; CompileAndVerify(text).VerifyIL("C.Main", expectedIL); } // Foreach value can't be deleted in a loop [Fact] public void TestRemoveValueInForeach() { var text = @"using System.Collections; using System.Collections.Generic; class C { static public void Main() { List<int> arrInt = new List<int>(); arrInt.Add(1); foreach (int i in arrInt) { arrInt.Remove(i);//It will generate error in run-time } } } "; string expectedIL = @"{ // Code size 64 (0x40) .maxstack 2 .locals init (System.Collections.Generic.List<int> V_0, //arrInt System.Collections.Generic.List<int>.Enumerator V_1, int V_2) //i IL_0000: newobj ""System.Collections.Generic.List<int>..ctor()"" IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: ldc.i4.1 IL_0008: callvirt ""void System.Collections.Generic.List<int>.Add(int)"" IL_000d: ldloc.0 IL_000e: callvirt ""System.Collections.Generic.List<int>.Enumerator System.Collections.Generic.List<int>.GetEnumerator()"" IL_0013: stloc.1 .try { IL_0014: br.s IL_0026 IL_0016: ldloca.s V_1 IL_0018: call ""int System.Collections.Generic.List<int>.Enumerator.Current.get"" IL_001d: stloc.2 IL_001e: ldloc.0 IL_001f: ldloc.2 IL_0020: callvirt ""bool System.Collections.Generic.List<int>.Remove(int)"" IL_0025: pop IL_0026: ldloca.s V_1 IL_0028: call ""bool System.Collections.Generic.List<int>.Enumerator.MoveNext()"" IL_002d: brtrue.s IL_0016 IL_002f: leave.s IL_003f } finally { IL_0031: ldloca.s V_1 IL_0033: constrained. ""System.Collections.Generic.List<int>.Enumerator"" IL_0039: callvirt ""void System.IDisposable.Dispose()"" IL_003e: endfinally } IL_003f: ret }"; CompileAndVerify(text).VerifyIL("C.Main", expectedIL); } // With multidimensional arrays, you can use one loop to iterate through the elements [Fact] public void TestMultiDimensionalArray() { var text = @"class T { static public void Main() { int[,] numbers2D = new int[3, 2] { { 9, 99 }, { 3, 33 }, { 5, 55 } }; foreach (int i in numbers2D) { System.Console.WriteLine(i); } } } "; string expectedOutput = @"9 99 3 33 5 55"; CompileAndVerify(text, expectedOutput: expectedOutput); } [WorkItem(540917, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540917")] [Fact] public void TestArray() { var text = @"using System; public class Test { static void Main(string[] args) { string[] arr = new string[4]; // Initialize arr[0] = ""one""; // Element 1 arr[1] = ""two""; // Element 2 foreach (string s in arr) { System.Console.WriteLine(s); } } } "; string expectedIL = @" { // Code size 46 (0x2e) .maxstack 4 .locals init (string[] V_0, int V_1) IL_0000: ldc.i4.4 IL_0001: newarr ""string"" IL_0006: dup IL_0007: ldc.i4.0 IL_0008: ldstr ""one"" IL_000d: stelem.ref IL_000e: dup IL_000f: ldc.i4.1 IL_0010: ldstr ""two"" IL_0015: stelem.ref IL_0016: stloc.0 IL_0017: ldc.i4.0 IL_0018: stloc.1 IL_0019: br.s IL_0027 IL_001b: ldloc.0 IL_001c: ldloc.1 IL_001d: ldelem.ref IL_001e: call ""void System.Console.WriteLine(string)"" IL_0023: ldloc.1 IL_0024: ldc.i4.1 IL_0025: add IL_0026: stloc.1 IL_0027: ldloc.1 IL_0028: ldloc.0 IL_0029: ldlen IL_002a: conv.i4 IL_002b: blt.s IL_001b IL_002d: ret }"; CompileAndVerify(text).VerifyIL("Test.Main", expectedIL); } [Fact] public void TestSpan() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { var sp = new Span<int>(new[] {1, 2, 3}); foreach(var i in sp) { Console.Write(i); } } } ", TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: "123").VerifyIL("Test.Main", @" { // Code size 56 (0x38) .maxstack 3 .locals init (System.Span<int> V_0, int V_1) IL_0000: ldc.i4.3 IL_0001: newarr ""int"" IL_0006: dup IL_0007: ldtoken ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=12 <PrivateImplementationDetails>.4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D"" IL_000c: call ""void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)"" IL_0011: newobj ""System.Span<int>..ctor(int[])"" IL_0016: stloc.0 IL_0017: ldc.i4.0 IL_0018: stloc.1 IL_0019: br.s IL_002d IL_001b: ldloca.s V_0 IL_001d: ldloc.1 IL_001e: call ""ref int System.Span<int>.this[int].get"" IL_0023: ldind.i4 IL_0024: call ""void System.Console.Write(int)"" IL_0029: ldloc.1 IL_002a: ldc.i4.1 IL_002b: add IL_002c: stloc.1 IL_002d: ldloc.1 IL_002e: ldloca.s V_0 IL_0030: call ""int System.Span<int>.Length.get"" IL_0035: blt.s IL_001b IL_0037: ret }"); } [Fact] public void TestSpanSideeffectingLoopBody() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { var sp = new Span<int>(new[] {1, 2, 3}); foreach(var i in sp) { Console.Write(i); sp = default; } Console.Write(sp.Length); } } ", TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: "1230").VerifyIL("Test.Main", @" { // Code size 79 (0x4f) .maxstack 4 .locals init (System.Span<int> V_0, //sp System.Span<int> V_1, int V_2) IL_0000: ldloca.s V_0 IL_0002: ldc.i4.3 IL_0003: newarr ""int"" IL_0008: dup IL_0009: ldtoken ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=12 <PrivateImplementationDetails>.4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D"" IL_000e: call ""void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)"" IL_0013: call ""System.Span<int>..ctor(int[])"" IL_0018: ldloc.0 IL_0019: stloc.1 IL_001a: ldc.i4.0 IL_001b: stloc.2 IL_001c: br.s IL_0038 IL_001e: ldloca.s V_1 IL_0020: ldloc.2 IL_0021: call ""ref int System.Span<int>.this[int].get"" IL_0026: ldind.i4 IL_0027: call ""void System.Console.Write(int)"" IL_002c: ldloca.s V_0 IL_002e: initobj ""System.Span<int>"" IL_0034: ldloc.2 IL_0035: ldc.i4.1 IL_0036: add IL_0037: stloc.2 IL_0038: ldloc.2 IL_0039: ldloca.s V_1 IL_003b: call ""int System.Span<int>.Length.get"" IL_0040: blt.s IL_001e IL_0042: ldloca.s V_0 IL_0044: call ""int System.Span<int>.Length.get"" IL_0049: call ""void System.Console.Write(int)"" IL_004e: ret }"); } [Fact] public void TestReadOnlySpan() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { var sp = new ReadOnlySpan<Color>(new [] {Color.Red, Color.Green, Color.Blue}); foreach(var i in sp) { Console.Write(i); } } } ", TestOptions.ReleaseExe); //NOTE: the verification error is expected. Wrapping of literals into readonly spans uses unsafe Span.ctor. CompileAndVerify(comp, expectedOutput: "RedGreenBlue", verify: Verification.Fails).VerifyIL("Test.Main", @" { // Code size 50 (0x32) .maxstack 2 .locals init (System.ReadOnlySpan<System.Color> V_0, int V_1) IL_0000: ldsflda ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=3 <PrivateImplementationDetails>.AE4B3280E56E2FAF83F414A6E3DABE9D5FBE18976544C05FED121ACCB85B53FC"" IL_0005: ldc.i4.3 IL_0006: newobj ""System.ReadOnlySpan<System.Color>..ctor(void*, int)"" IL_000b: stloc.0 IL_000c: ldc.i4.0 IL_000d: stloc.1 IL_000e: br.s IL_0027 IL_0010: ldloca.s V_0 IL_0012: ldloc.1 IL_0013: call ""ref readonly System.Color System.ReadOnlySpan<System.Color>.this[int].get"" IL_0018: ldind.i1 IL_0019: box ""System.Color"" IL_001e: call ""void System.Console.Write(object)"" IL_0023: ldloc.1 IL_0024: ldc.i4.1 IL_0025: add IL_0026: stloc.1 IL_0027: ldloc.1 IL_0028: ldloca.s V_0 IL_002a: call ""int System.ReadOnlySpan<System.Color>.Length.get"" IL_002f: blt.s IL_0010 IL_0031: ret }"); } [ConditionalFact(typeof(CoreClrOnly))] public void TestReadOnlySpanString() { var comp = CreateCompilation(@" using System; class Test { public static void Main() { var sp = (ReadOnlySpan<char>)""hello""; foreach(var i in sp) { Console.Write(i); } } } ", targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: "hello", verify: Verification.Passes).VerifyIL("Test.Main", @" { // Code size 44 (0x2c) .maxstack 2 .locals init (System.ReadOnlySpan<char> V_0, int V_1) IL_0000: ldstr ""hello"" IL_0005: call ""System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldc.i4.0 IL_000c: stloc.1 IL_000d: br.s IL_0021 IL_000f: ldloca.s V_0 IL_0011: ldloc.1 IL_0012: call ""ref readonly char System.ReadOnlySpan<char>.this[int].get"" IL_0017: ldind.u2 IL_0018: call ""void System.Console.Write(char)"" IL_001d: ldloc.1 IL_001e: ldc.i4.1 IL_001f: add IL_0020: stloc.1 IL_0021: ldloc.1 IL_0022: ldloca.s V_0 IL_0024: call ""int System.ReadOnlySpan<char>.Length.get"" IL_0029: blt.s IL_000f IL_002b: ret }"); } [Fact] public void TestReadOnlySpan2() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { foreach(var i in (ReadOnlySpan<byte>)new byte[] {1, 2, 3}) { Console.Write(i); } } } ", TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: "123", verify: Verification.Fails).VerifyIL("Test.Main", @" { // Code size 45 (0x2d) .maxstack 2 .locals init (System.ReadOnlySpan<byte> V_0, int V_1) IL_0000: ldsflda ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=3 <PrivateImplementationDetails>.039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81"" IL_0005: ldc.i4.3 IL_0006: newobj ""System.ReadOnlySpan<byte>..ctor(void*, int)"" IL_000b: stloc.0 IL_000c: ldc.i4.0 IL_000d: stloc.1 IL_000e: br.s IL_0022 IL_0010: ldloca.s V_0 IL_0012: ldloc.1 IL_0013: call ""ref readonly byte System.ReadOnlySpan<byte>.this[int].get"" IL_0018: ldind.u1 IL_0019: call ""void System.Console.Write(int)"" IL_001e: ldloc.1 IL_001f: ldc.i4.1 IL_0020: add IL_0021: stloc.1 IL_0022: ldloc.1 IL_0023: ldloca.s V_0 IL_0025: call ""int System.ReadOnlySpan<byte>.Length.get"" IL_002a: blt.s IL_0010 IL_002c: ret }"); } [Fact] public void TestSpanNoIndexer() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { var sp = new Span<int>(new[] {1, 2, 3}); foreach(var i in sp) { Console.Write(i); } } } ", TestOptions.ReleaseExe); comp.MakeMemberMissing(WellKnownMember.System_Span_T__get_Item); CompileAndVerify(comp, expectedOutput: "123").VerifyIL("Test.Main", @" { // Code size 57 (0x39) .maxstack 4 .locals init (System.Span<int> V_0, //sp System.Span<int>.Enumerator V_1) IL_0000: ldloca.s V_0 IL_0002: ldc.i4.3 IL_0003: newarr ""int"" IL_0008: dup IL_0009: ldtoken ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=12 <PrivateImplementationDetails>.4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D"" IL_000e: call ""void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)"" IL_0013: call ""System.Span<int>..ctor(int[])"" IL_0018: ldloca.s V_0 IL_001a: call ""System.Span<int>.Enumerator System.Span<int>.GetEnumerator()"" IL_001f: stloc.1 IL_0020: br.s IL_002f IL_0022: ldloca.s V_1 IL_0024: call ""ref int System.Span<int>.Enumerator.Current.get"" IL_0029: ldind.i4 IL_002a: call ""void System.Console.Write(int)"" IL_002f: ldloca.s V_1 IL_0031: call ""bool System.Span<int>.Enumerator.MoveNext()"" IL_0036: brtrue.s IL_0022 IL_0038: ret }"); } [Fact] public void TestSpanValIndexer() { var comp = CreateEmptyCompilation(@" using System; class Test { public static void Main() { var sp = new ReadOnlySpan<int>(new[] {1, 2, 3}); foreach(var i in sp) { Console.Write(i); } } } namespace System { public readonly ref struct ReadOnlySpan<T> { private readonly T[] arr; public T this[int i] => arr[i]; public int Length { get; } public ReadOnlySpan(T[] arr) { this.arr = arr; this.Length = arr.Length; } public Enumerator GetEnumerator() => new Enumerator(this); public ref struct Enumerator { private readonly ReadOnlySpan<T> _span; private int _index; internal Enumerator(ReadOnlySpan<T> span) { _span = span; _index = -1; } public bool MoveNext() { int index = _index + 1; if (index < _span.Length) { _index = index; return true; } return false; } public T Current { get => _span[_index]; } } } } ", references: new[] { MscorlibRef_v4_0_30316_17626 }, TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: "123", verify: Verification.Fails).VerifyIL("Test.Main", @" { // Code size 56 (0x38) .maxstack 4 .locals init (System.ReadOnlySpan<int> V_0, //sp System.ReadOnlySpan<int>.Enumerator V_1) IL_0000: ldloca.s V_0 IL_0002: ldc.i4.3 IL_0003: newarr ""int"" IL_0008: dup IL_0009: ldtoken ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=12 <PrivateImplementationDetails>.4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D"" IL_000e: call ""void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)"" IL_0013: call ""System.ReadOnlySpan<int>..ctor(int[])"" IL_0018: ldloca.s V_0 IL_001a: call ""System.ReadOnlySpan<int>.Enumerator System.ReadOnlySpan<int>.GetEnumerator()"" IL_001f: stloc.1 IL_0020: br.s IL_002e IL_0022: ldloca.s V_1 IL_0024: call ""int System.ReadOnlySpan<int>.Enumerator.Current.get"" IL_0029: call ""void System.Console.Write(int)"" IL_002e: ldloca.s V_1 IL_0030: call ""bool System.ReadOnlySpan<int>.Enumerator.MoveNext()"" IL_0035: brtrue.s IL_0022 IL_0037: ret }"); } [Fact] public void TestSpanConvert() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { var sp = new Span<int>(new[] {1, 2, 3}); foreach(byte i in sp) { Console.Write(i); } } } ", TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: "123").VerifyIL("Test.Main", @" { // Code size 57 (0x39) .maxstack 3 .locals init (System.Span<int> V_0, int V_1) IL_0000: ldc.i4.3 IL_0001: newarr ""int"" IL_0006: dup IL_0007: ldtoken ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=12 <PrivateImplementationDetails>.4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D"" IL_000c: call ""void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)"" IL_0011: newobj ""System.Span<int>..ctor(int[])"" IL_0016: stloc.0 IL_0017: ldc.i4.0 IL_0018: stloc.1 IL_0019: br.s IL_002e IL_001b: ldloca.s V_0 IL_001d: ldloc.1 IL_001e: call ""ref int System.Span<int>.this[int].get"" IL_0023: ldind.i4 IL_0024: conv.u1 IL_0025: call ""void System.Console.Write(int)"" IL_002a: ldloc.1 IL_002b: ldc.i4.1 IL_002c: add IL_002d: stloc.1 IL_002e: ldloc.1 IL_002f: ldloca.s V_0 IL_0031: call ""int System.Span<int>.Length.get"" IL_0036: blt.s IL_001b IL_0038: ret }"); } [Fact] public void TestSpanDeconstruct() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { static void Main(string[] args) { var sp = new Span<(int, int)>(new[] {(1, 2), (3, 4)}); foreach(var (i, j) in sp) { Console.Write(i); Console.Write(j); } } } ", TestOptions.ReleaseExe); comp = comp.WithReferences(comp.References.Concat(new[] { SystemRuntimeFacadeRef, ValueTupleRef })); CompileAndVerify(comp, expectedOutput: "1234").VerifyIL("Test.Main", @" { // Code size 95 (0x5f) .maxstack 5 .locals init (System.Span<System.ValueTuple<int, int>> V_0, int V_1, int V_2) //i IL_0000: ldc.i4.2 IL_0001: newarr ""System.ValueTuple<int, int>"" IL_0006: dup IL_0007: ldc.i4.0 IL_0008: ldc.i4.1 IL_0009: ldc.i4.2 IL_000a: newobj ""System.ValueTuple<int, int>..ctor(int, int)"" IL_000f: stelem ""System.ValueTuple<int, int>"" IL_0014: dup IL_0015: ldc.i4.1 IL_0016: ldc.i4.3 IL_0017: ldc.i4.4 IL_0018: newobj ""System.ValueTuple<int, int>..ctor(int, int)"" IL_001d: stelem ""System.ValueTuple<int, int>"" IL_0022: newobj ""System.Span<System.ValueTuple<int, int>>..ctor(System.ValueTuple<int, int>[])"" IL_0027: stloc.0 IL_0028: ldc.i4.0 IL_0029: stloc.1 IL_002a: br.s IL_0054 IL_002c: ldloca.s V_0 IL_002e: ldloc.1 IL_002f: call ""ref System.ValueTuple<int, int> System.Span<System.ValueTuple<int, int>>.this[int].get"" IL_0034: ldobj ""System.ValueTuple<int, int>"" IL_0039: dup IL_003a: ldfld ""int System.ValueTuple<int, int>.Item1"" IL_003f: stloc.2 IL_0040: ldfld ""int System.ValueTuple<int, int>.Item2"" IL_0045: ldloc.2 IL_0046: call ""void System.Console.Write(int)"" IL_004b: call ""void System.Console.Write(int)"" IL_0050: ldloc.1 IL_0051: ldc.i4.1 IL_0052: add IL_0053: stloc.1 IL_0054: ldloc.1 IL_0055: ldloca.s V_0 IL_0057: call ""int System.Span<System.ValueTuple<int, int>>.Length.get"" IL_005c: blt.s IL_002c IL_005e: ret }"); } [Fact] public void TestSpanConvertDebug() { var comp = CreateCompilationWithMscorlibAndSpan(@" using System; class Test { public static void Main() { var sp = new Span<int>(new[] {1, 2, 3}); foreach(byte i in sp) { Console.Write(i); } } } ", TestOptions.DebugExe); CompileAndVerify(comp, expectedOutput: "123").VerifyIL("Test.Main", @" { // Code size 67 (0x43) .maxstack 4 .locals init (System.Span<int> V_0, //sp System.Span<int> V_1, int V_2, byte V_3) //i IL_0000: nop IL_0001: ldloca.s V_0 IL_0003: ldc.i4.3 IL_0004: newarr ""int"" IL_0009: dup IL_000a: ldtoken ""<PrivateImplementationDetails>.__StaticArrayInitTypeSize=12 <PrivateImplementationDetails>.4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D"" IL_000f: call ""void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)"" IL_0014: call ""System.Span<int>..ctor(int[])"" IL_0019: nop IL_001a: ldloc.0 IL_001b: stloc.1 IL_001c: ldc.i4.0 IL_001d: stloc.2 IL_001e: br.s IL_0038 IL_0020: ldloca.s V_1 IL_0022: ldloc.2 IL_0023: call ""ref int System.Span<int>.this[int].get"" IL_0028: ldind.i4 IL_0029: conv.u1 IL_002a: stloc.3 IL_002b: nop IL_002c: ldloc.3 IL_002d: call ""void System.Console.Write(int)"" IL_0032: nop IL_0033: nop IL_0034: ldloc.2 IL_0035: ldc.i4.1 IL_0036: add IL_0037: stloc.2 IL_0038: ldloc.2 IL_0039: ldloca.s V_1 IL_003b: call ""int System.Span<int>.Length.get"" IL_0040: blt.s IL_0020 IL_0042: ret }"); } // Traveled Multi-dimensional jagged arrays [WorkItem(9229, "DevDiv_Projects/Roslyn")] [Fact] public void TestJaggedArray() { var text = @"using System; public class Test { static void Main(string[] args) { int[][] arr = new int[][] { new int[] { 1, 2 }, new int[] { 4, 5, 6 } }; foreach (int[] outer in arr) { foreach (int i in outer) { Console.WriteLine(i); } } } } "; string expectedOutput = @"1 2 4 5 6"; CompileAndVerify(text, expectedOutput: expectedOutput); } // Optimization to foreach (char c in String) by treating String as a char array [Fact] public void TestString01() { var text = @"using System; public class Test { static void Main(string[] args) { System.String Str = new System.String('\0', 1024); foreach (char C in Str) { } } } "; string expectedOutput = @""; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestString02() { var text = @"using System; public class Test { static public int Main(string[] args) { foreach (var var in ""goo"") { if (!var.GetType().Equals(typeof(char))) { System.Console.WriteLine(-1); return -1; } } return 0; } } "; string expectedOutput = @""; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestString03() { var text = @"using System; public class Test { static public void Main(string[] args) { String Str = null; foreach (char C in Str) { } } } "; string expectedIL = @"{ // Code size 28 (0x1c) .maxstack 2 .locals init (string V_0, int V_1) IL_0000: ldnull IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: br.s IL_0012 IL_0006: ldloc.0 IL_0007: ldloc.1 IL_0008: callvirt ""char string.this[int].get"" IL_000d: pop IL_000e: ldloc.1 IL_000f: ldc.i4.1 IL_0010: add IL_0011: stloc.1 IL_0012: ldloc.1 IL_0013: ldloc.0 IL_0014: callvirt ""int string.Length.get"" IL_0019: blt.s IL_0006 IL_001b: ret }"; CompileAndVerify(text).VerifyIL("Test.Main", expectedIL); } // Traversing items in 'Dictionary' [Fact] public void TestDictionary() { var text = @"using System; using System.Collections.Generic; public class Test { static public void Main(string[] args) { Dictionary<int, int> s = new Dictionary<int, int>(); s.Add(1, 2); s.Add(2, 3); s.Add(3, 4); foreach (var pair in s) { Console.WriteLine( pair.Key );} foreach (KeyValuePair<int, int> pair in s) {Console.WriteLine( pair.Value ); } } } "; string expectedOutput = @"1 2 3 2 3 4"; CompileAndVerify(text, expectedOutput: expectedOutput); } // Inner foreach loop referencing the outer foreach loop iteration variable [Fact] public void TestNestedLoop() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (string x in S) { foreach (char y in x) { System.Console.WriteLine(y); } } } } "; string expectedOutput = @"A B C X Y Z"; CompileAndVerify(text, expectedOutput: expectedOutput); } // Breaking from nested Loops [Fact] public void TestBreakInNestedLoop() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (string x in S) { foreach (char y in x) { if (y == 'A') break; else System.Console.WriteLine(y); } System.Console.WriteLine(x); } } } "; string expectedOutput = @"ABC X Y Z XYZ"; CompileAndVerify(text, expectedOutput: expectedOutput); } // Continuing from nested Loops [Fact] public void TestContinueInNestedLoop() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (string x in S) { foreach (char y in x) { if (y == 'C') continue; System.Console.WriteLine(y); } } } } "; string expectedOutput = @"A B X Y Z"; CompileAndVerify(text, expectedOutput: expectedOutput); } // Goto in foreach loops [Fact] public void TestGoto01() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (string x in S) { foreach (char y in x) { System.Console.WriteLine(y); goto stop; } } stop: return; } } "; string expectedOutput = @"A"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestGoto02() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (string x in S) { foreach (char y in x) { System.Console.WriteLine(y); goto outerLoop; } outerLoop: return; } } } "; string expectedOutput = @"A"; CompileAndVerify(text, expectedOutput: expectedOutput); } // 'Return' in foreach [Fact] public void TestReturn() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (string x in S) { return; } } } "; string expectedIL = @"{ // Code size 39 (0x27) .maxstack 4 .locals init (string[] V_0, int V_1) IL_0000: ldc.i4.2 IL_0001: newarr ""string"" IL_0006: dup IL_0007: ldc.i4.0 IL_0008: ldstr ""ABC"" IL_000d: stelem.ref IL_000e: dup IL_000f: ldc.i4.1 IL_0010: ldstr ""XYZ"" IL_0015: stelem.ref IL_0016: stloc.0 IL_0017: ldc.i4.0 IL_0018: stloc.1 IL_0019: br.s IL_0020 IL_001b: ldloc.0 IL_001c: ldloc.1 IL_001d: ldelem.ref IL_001e: pop IL_001f: ret IL_0020: ldloc.1 IL_0021: ldloc.0 IL_0022: ldlen IL_0023: conv.i4 IL_0024: blt.s IL_001b IL_0026: ret } "; CompileAndVerify(text).VerifyIL("Test.Main", expectedIL); } // Dynamic works in foreach [Fact] public void TestDynamic() { var text = @"public class Test { static public void Main(string[] args) { string[] S = new string[] { ""ABC"", ""XYZ"" }; foreach (dynamic x in S) { System.Console.WriteLine(x.ToLower()); } } } "; string expectedOutput = @"abc xyz"; CompileAndVerify(text, new[] { CSharpRef }, expectedOutput: expectedOutput); } [Fact] public void TestVar01() { var text = @"using System.Collections.Generic; public class Test { static public void Main(string[] args) { foreach (var var in new List<double> { 1.0, 2.0, 3.0 }) { if (var.GetType().Equals(typeof(double))) { System.Console.WriteLine(true); } } } } "; string expectedOutput = @"True True True"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestVar02() { var text = @" public class Test { static public void Main(string[] args) { foreach (var var in new string[] { ""one"", ""two"", ""three"" }) { if (!var.GetType().Equals(typeof(double))) { System.Console.WriteLine(false); } } } } "; string expectedOutput = @"False False False"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestVar03() { var text = @" public class Test { static public void Main(string[] args) { foreach (var var in new MyClass()) { if (var.GetType().Equals(typeof(int))) { System.Console.WriteLine(true); } } } } class MyClass { public MyEnumerator GetEnumerator() { return new MyEnumerator(); } } class MyEnumerator { int count = 4; public int Current { get { return count; } } public bool MoveNext() { count--; return count != 0; } } "; string expectedOutput = @"True True True"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestQuery() { var text = @" using System.Linq; public class Test { static public void Main(string[] args) { foreach (var x in from x in new[] { 'A', 'B', 'C' } let z = x.ToString() select z into w select w) { System.Console.WriteLine(x.ToLower()); } } } "; string expectedOutput = @"a b c "; var comp = CreateCompilationWithMscorlib40AndSystemCore(text, options: TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: expectedOutput); } [Fact] public void TestYield01() { var text = @" using System.Collections; public class Test { public static void Main(string[] args) { foreach (int i in myClass.Power(2, 8)) { System.Console.WriteLine(""{0}"", i); } } } public class myClass { public static IEnumerable Power(int number, int exponent) { int counter = 0; int result = 1; while (counter++ < exponent) { result = result * number; yield return result; } } } "; string expectedOutput = @"2 4 8 16 32 64 128 256 "; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestYield02() { var text = @" using System.Collections.Generic; public class Test { public static void Main(string[] args) { foreach (int i in FromTo(2,4)) { System.Console.WriteLine(""{0}"", i); } } public static IEnumerable<int> FromTo(int from, int to) { for (int i = from; i <= to; i++) yield return i; } } "; string expectedOutput = @"2 3 4 "; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestYield03() { var text = @" using System.Collections.Generic; public class Test { public static void Main(string[] args) { foreach (var i in EnumerateIt<string>(new List<string>() { ""abc"" })) { System.Console.WriteLine(i); } } public static IEnumerable<T> EnumerateIt<T>(IEnumerable<T> xs) { foreach (T x in xs) yield return x; } } "; string expectedOutput = @"abc"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestYield04() { var text = @" using System.Collections.Generic; public class Test { public static void Main(string[] args) { foreach (int p in EnumerateIt(FromTo(3, 5))) { System.Console.WriteLine(""{0}"", p); } } public static IEnumerable<int> FromTo(int from, int to) { for (int i = from; i <= to; i++) yield return i; } public static IEnumerable<T> EnumerateIt<T>(IEnumerable<T> xs) { foreach (T x in xs) yield return x; } } "; string expectedOutput = @"3 4 5"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestYield05() { var text = @" using System.Collections.Generic; public class Test { public static void Main(string[] args) { foreach (var j in new Gen<double>()) { System.Console.WriteLine(j); } } } public class Gen<T> where T : new() { public IEnumerator<T> GetEnumerator() { yield return new T(); yield return new T(); yield return new T(); } } "; string expectedOutput = @"0 0 0"; CompileAndVerify(text, expectedOutput: expectedOutput); } [Fact] public void TestValueTypeIterationVariableCanBeMutatedByInstanceMethods() { const string source = @" struct A { int field; void Set(A a) { this = a; } static void Main() { foreach (var a in new A[1]) { a.Set(new A { field = 5 }); System.Console.Write(a.field); } } }"; CompileAndVerify(source, expectedOutput: "5"); } [Fact, WorkItem(1077204, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1077204")] public void TestValueTypeIterationVariableFieldsAreReadonly() { const string source = @" using System; struct A { public B B; static void Main() { A[] array = { default(A) }; foreach (A a in array) { a.B.SetField(5); Console.Write(a.B.Field); } } } struct B { public int Field; public void SetField(int value) { this.Field = value; } }"; CompileAndVerify(source, expectedOutput: "0"); } [Fact] public void TestValueTypeIterationVariableFieldsAreReadonly2() { const string source = @" struct C { public int field; public void SetField(int value) { field = value; } } struct B { public C c; } struct A { B b; static void Main() { foreach (var a in new A[1]) { a.b.c.SetField(5); System.Console.Write(a.b.c.field); } } }"; CompileAndVerify(source, expectedOutput: "0"); } [Fact] public void Var_ExtensionGetEnumerator() { var source = """ using System.Collections.Generic; class MyCollection<T> { public readonly List<T> Items; public MyCollection(params T[] items) { Items = new(items); } } static class Extensions { public static IEnumerator<T> GetEnumerator<T>(this MyCollection<T> c) => c.Items.GetEnumerator(); } class Program { static void Main() { MyCollection<int> x = new(1, 2, 3); int total = 0; foreach (var y in x) total += y; } } """; var comp = CreateCompilation(source, parseOptions: TestOptions.RegularDefault.WithFeature(Feature.RunNullableAnalysis, "never")); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var decl = tree.GetRoot().DescendantNodes().OfType<ForEachStatementSyntax>().Single(); var local = (SourceLocalSymbol)model.GetDeclaredSymbol(decl).GetSymbol<LocalSymbol>(); Assert.True(local.IsVar); Assert.Equal("System.Int32", local.Type.ToTestDisplayString()); comp.VerifyDiagnostics(); } } }