控制流程语句

控制流程语句在类C风格语言中都比较相似,这里我们简单介绍下C#中各种控制流程语句的写法,此外关于循环,我们这里额外介绍可迭代对象的接口以及生成器函数相关的知识。

判断

if else 语句

if else语句根据括号中的表达式返回truefalse,执行对应的逻辑分支,判断的顺序是从上到下,且只会进入一个条件分支,也就是说如果有多个条件分支满足判断,也只会进入第一个。

if (TestExpr)
    Statement1
else if (TestExpr)
    Statement2
else
    Statement3

这里要注意的是和C/C++/JavaScript不同,C#中判断分支语句必须严格符合bool类型,而不能使用int等。

switch case 语句

switch case语句中,和所有类C语言一样,不要忘记每个分支最后的break

switch (TestExpr)
    case (PatternExpression)
        Statement
        break
    default
        Statement
        break

循环

while 语句

while语句会一直执行循环体的代码,直到判断条件为false

while (TestExpr)
    Statement

do while 语句

do while语句和while语句类似,只不过是先执行循环体再判断。

do
    Statement
while (TestExpr)

for 语句

for循环语句通常会引入一个计数变量,用于自增或自减,直到满足条件。

for (Initializer; TestExpr; InterationExpr)
    Statement

对于这种简单的计数变量、临时变量之类,我们可以使用类型推导来简化代码。

for (var i = 0; i < 3; i++)
{
    Console.WriteLine(i);
}

foreach 语句

C#中foreach语句可以不使用计数变量直接循环一个可迭代对象,例子如下。

int[] arr = { 1, 2, 3 };
foreach (int i in arr)
{
    Console.WriteLine(i);
}

IEnumerable和IEnumerator

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迭代时,只能得到12

我们已经学习了如何使用生成器函数编写可迭代对象,这里我们再详细分析一下生成器函数的执行流程。

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关键字我们都不陌生,它最常用的两个用法分别是引入命名空间,和为指定命名空间创建别名。然而using还有一个全新的用法,即确保调用IDisposable对象的Dispose方法。

using (var font = new Font("Arial", 10.0f))
{
    byte charset = font.GdiCharSet;
}

font对象包含一些非托管资源,使用完成后我们需要调用Dispose方法将这些资源释放,而且通常可能还会包含一系列的try...catch...语句确保方法的执行,否则会造成内存泄漏。using语句则简化了这些代码的编写,能够自动在语句块结束后确保Dispose被调用。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap