控制流程语句在类C风格语言中都比较相似,这里我们简单介绍下C#中各种控制流程语句的写法,此外关于循环,我们这里额外介绍可迭代对象的接口以及生成器函数相关的知识。
if else
语句根据括号中的表达式返回true
或false
,执行对应的逻辑分支,判断的顺序是从上到下,且只会进入一个条件分支,也就是说如果有多个条件分支满足判断,也只会进入第一个。
if (TestExpr)
Statement1
else if (TestExpr)
Statement2
else
Statement3
这里要注意的是和C/C++/JavaScript不同,C#中判断分支语句必须严格符合bool
类型,而不能使用int
等。
switch case
语句中,和所有类C语言一样,不要忘记每个分支最后的break
。
switch (TestExpr)
case (PatternExpression)
Statement
break
default
Statement
break
while
语句会一直执行循环体的代码,直到判断条件为false
。
while (TestExpr)
Statement
do while
语句和while
语句类似,只不过是先执行循环体再判断。
do
Statement
while (TestExpr)
for
循环语句通常会引入一个计数变量,用于自增或自减,直到满足条件。
for (Initializer; TestExpr; InterationExpr)
Statement
对于这种简单的计数变量、临时变量之类,我们可以使用类型推导来简化代码。
for (var i = 0; i < 3; i++)
{
Console.WriteLine(i);
}
C#中foreach
语句可以不使用计数变量直接循环一个可迭代对象,例子如下。
int[] arr = { 1, 2, 3 };
foreach (int i in arr)
{
Console.WriteLine(i);
}
foreach
语句中所谓的可迭代对象,实际上就是指实现了IEnumerable
接口的类,实际上我们可以很方便的使用生成器函数创建可迭代对象,下面是一段例子代码。
namespace Gacfox.Demo.Demonet
{
public class Program
{
IEnumerable<int> Foo()
{
yield return 1;
yield return 2;
yield return 3;
yield return 4;
}
static void Main()
{
Program program = new Program();
IEnumerator<int> enumerator = program.Foo().GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
}
}
代码中,Foo()
是我们自定义的生成器函数,其返回值为IEnumerable<int>
类型。这里要注意,System.Collections.Generic
命名空间中包含的IEnumerable<>
是必须带泛型的,而System.Collections
命名空间下还有一个不带泛型的IEnumerable
,这是历史原因造成的,通常来说我们没必要使用后者。
有关生成器函数,其实使用方法很简单,生成器函数返回值一定是IEnumerable<>
(或IEnumerable
)即可迭代对象,其内部使用yield
语句返回若干值,在对其返回的可迭代对象进行迭代时,对应yield return
的值就会被顺序取出。
在Main()
函数中的代码演示了这一过程的基本原理,代码中我们使用了GetEnumerator()
方法获取了IEnumerator<int>
类型的对象,并用MoveNext()
方法判断迭代是否完成,同时使用Current
属性取出当前迭代的值。
实际开发中,我们不会像上面Main()
方法中的代码来使用可迭代对象,一般都使用foreach
语句。
static void Main()
{
Program program = new Program();
foreach (int i in program.Foo())
{
Console.WriteLine(i);
}
}
前面已经介绍过,生成器函数返回值一定是IEnumerable<>
(或IEnumerable
)即可迭代对象,其内部使用yield
语句返回若干值,在对其返回的可迭代对象进行foreach
迭代时,对应yield return
的值就会被顺序取出。
IEnumerable<int> Foo()
{
yield return 1;
yield return 2;
yield return 3;
yield return 4;
}
此外,我们还可以使用yield break
终止生成器函数的迭代,例子如下:
IEnumerable<int> Foo()
{
yield return 1;
yield return 2;
yield break;
yield return 4;
}
上面代码我们使用foreach
迭代时,只能得到1
和2
。
我们已经学习了如何使用生成器函数编写可迭代对象,这里我们再详细分析一下生成器函数的执行流程。
namespace Gacfox.Demo.Demonet
{
public class Program
{
IEnumerable<int> Foo()
{
Console.WriteLine("before yield return");
yield return 1;
Console.WriteLine("after yield return");
}
static void Main()
{
Program program = new Program();
IEnumerator<int> enumerator = program.Foo().GetEnumerator();
Console.WriteLine("before first call Foo");
enumerator.MoveNext();
Console.WriteLine("after first call Foo");
Console.WriteLine("before second call Foo");
enumerator.MoveNext();
Console.WriteLine("after second call Foo");
}
}
}
程序最终输出如下:
before first call Foo
before yield return
after first call Foo
before second call Foo
after yield return
after second call Foo
我们可以发现生成器函数的特点,我们第1次调用MoveNext()
时,生成器函数执行到yield return
就停止了,而实际上其后面还有一段代码没有执行;而第2次调用MoveNext()
时才会真正执行,然而此时已经没有可迭代对象的值了,因此不会再次执行到生成器函数的内部。
前面介绍了判断和循环语句,实际上跳转语句也属于控制流程语句,它们一般和判断、循环语句结合使用。跳转语句用法类C语言都是基本一致的,这里就不多介绍了。
break:用于跳出循环。
continue:用于结束当前循环体,直接开启下一次循环。
return:用于直接函数返回。
goto:用于无条件跳转到一个标签,通常在跳出多层循环时使用。
using
关键字我们都不陌生,它最常用的两个用法分别是引入命名空间,和为指定命名空间创建别名。然而using
还有一个全新的用法,即确保调用IDisposable
对象的Dispose
方法。
using (var font = new Font("Arial", 10.0f))
{
byte charset = font.GdiCharSet;
}
font
对象包含一些非托管资源,使用完成后我们需要调用Dispose
方法将这些资源释放,而且通常可能还会包含一系列的try...catch...
语句确保方法的执行,否则会造成内存泄漏。using
语句则简化了这些代码的编写,能够自动在语句块结束后确保Dispose
被调用。