C# 入门基础

C

也叫 C Sharp。

实际上应该是 C♯,但是 # 比 ♯ 更容易输入,因此逐渐变成了 c#。

C♯ 是一个音名,意为 C 升半音。

是一个简单的,现代的,由微软开发的通用的面向对象的编程语言。

C# 是基于 C 和 C++ 的。

程序结构

一个 C# 程序组成有:

  • 命名空间声明
  • 一个类
  • 类方法
  • 类属性
  • 一个 main 方法
  • 语句和表达式
  • 注释

C# 的代码文件以 .cs 为后缀。

C# 大小写敏感。

所有语句和表达式都必须以分号结尾。

程序执行从 Main 方法开始,因为应用程序启动后第一个调用 Main 方法。但是库或者服务不是必须的。

只能存在一个 Main, 否则必须使用编译器选项指定入口。

Main 方法必须是 static 静态的。

与 Java 不同,文件名可以不同于类名称。

// 一般程序中有多个 using 语句。
// using 关键字用于在进程中包含 System 命名空间。
using System;

// 命名空间声明
// 命名空间里包含有一系列的类
namespace  FirstApplication
{
  // 类 声明
  // 类一般包含多个方法
  // 方法定义了类的行为
  class FirstCSApplication
  {
    // Main 方法 是所有 C# 程序的入口点
    // Main 方法说明当执行时,类将进行什么操作
    static void Main (string[] args)
    {
      // 这是注释 在编译时会被忽略
      /* this is comment */

      // 通过此语句指定了行为
      // WriteLine 是定义在 System 命名空间中的
      // Console 类的一个方法
      Console.WriteLine('this is my first C# application!');

      // 此语句针对 vs .net
      // 使得程序需要等待一个按键动作而不是执行完毕而关闭
      Console.ReadKey()
    }
  }
}

编译和执行

通过命令行的形式。

将命令行切换到代码文件所在目录下。

执行 csc FileName 其中 FileName 就是代码文件名称。

如果没有报错,那么会在该目录下生成 FileName 同名的 exe 可执行文件。

如果没有 csc 命令,则需要手动将 .net 路径加入到环境配置中。

基本语法

C# 是面向对象的,在面向对象编程中,程序由各种相互交互的对象组成。

相同种类的对象通常具有相同类型,或者说,是在相同类中。

using System;

namespace BaseStatements {
  class BaseStatements {
    double length;
    double width;

    public void AcceptDetails () {
      length = 4.5;
      width = 3.5;
    }

    public double GetArea () {
      return length * width;
    }

    public void Output () {
      Console.WriteLine("length: {0}", length);
      Console.WriteLine("width: {0}", width);
      Console.WriteLine("area: {0}", GetArea());
    }
  }

  class ExecuteBaseStatements {
    static void Main (string[] args) {
      BaseStatements statements = new BaseStatements();

      statements.AcceptDetails();
      statements.Output();
      Console.ReadLine();
    }
  }
}

顶级语句

无需显式包含 Main 方法。

可以最大限度的减少必须编写的代码。

此情况下,编译器将为程序生成类和 Main 入口。

注释

// 单行注释
/*

多行注释

*/

XML 注释

通过 /// 进行 XML 注释。

会被编译,因此会影响编译速度,但是不会影响执行速度。

using System;

namespace BaseStatements {
  class BaseStatements {
    /// <remarks>
    /// 说明
    /// </remarks>
        static void Main (string[] args) {
      Console.ReadKey();
    }
  }
```xml
<remarks>对类型进行描述,功能类似<summary>,据说建议使用<remarks>;
<summary>对共有类型的类、方法、属性或字段进行注释;
<value>主要用于属性的注释,表示属性的制的含义,可以配合<summary>使用;
<param>用于对方法的参数进行说明,格式:<param name="param_name">value</param>;
<returns>用于定义方法的返回值,对于一个方法,输入///后,会自动添加<summary>、<param>列表和<returns>;
<exception>定义可能抛出的异常,格式:<exception cref="IDNotFoundException">;
<example>用于给出如何使用某个方法、属性或者字段的使用方法;
<permission>涉及方法的访问许可;
<seealso>用于参考某个其它的东东:),也可以通过cref设置属性;
<include>用于指示外部的XML注释;

  二级子标签
<c> or <code>主要用于加入代码段;
<para>的作用类似HTML中的<p>标记符,就是分段;
<pararef>用于引用某个参数;
<see>的作用类似<seealso>,可以指示其它的方法;
<list>用于生成一个列表;

另外,还可以自定义 XML 标签。

标识符

在 C# 中,类命名必须遵循:

  • 标识符以字母,下划线,@开头,后可跟字母,数字,下划线,@
  • 第一个字符不能是数字
  • 标识符必须不含任何嵌入的空格或符号
  • 标识符不能是 C# 关键字,除非是 @前缀
  • 标识符区分大小写
  • 不能与 C# 类库名称相同

数据类型

在 C# 中,变量分为:

  • 值类型
  • 引用类型
  • 指针类型

值类型

值类型变量可以直接分配给一个值。

它们是从 System.ValueType 中派生的。

值类型直接包含数据,如 intcharfloat,它们分别存储数字,字符,浮点数。当声明时,系统会自动分配内存存储。

不能直接从值类型派生新类型,但可以结构实现接口。

值类型不能有 null 值。

每个值类型都具有一个初始化该类型默认值的隐式默认构造函数。

如果需要得到一个类型或变量在特定平台上的大小,可使用 sizeof(type) 获取字节单位的存储对象类型的大小。

C# 2010 中可用值类型:

| 类型 | 描述 | 范围 | 默认值 |
| :—— | :———————————– | :—————————————————— | :—– |
| bool | 布尔值 | True 或 False | False |
| byte | 8 位无符号整!数 | 0 到 255 | 0 |
| char | 16 位 Unicode 字符 | U +0000 到 U +ffff | '\0' |
| decimal | 128 位精确的十进制值,28-29 有效位数 | (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 | 0.0M |
| double | 64 位双精度浮点型 | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 | 0.0D |
| float | 32 位单精度浮点型 | -3.4 x 1038 到 + 3.4 x 1038 | 0.0F |
| int | 32 位有符号整数类型 | -2,147,483,648 到 2,147,483,647 | 0 |
| long | 64 位有符号整数类型 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0L |
| sbyte | 8 位有符号整数类型 | -128 到 127 | 0 |
| short | 16 位有符号整数类型 | -32,768 到 32,767 | 0 |
| uint | 32 位无符号整数类型 | 0 到 4,294,967,295 | 0 |
| ulong | 64 位无符号整数类型 | 0 到 18,446,744,073,709,551,615 | 0 |
| ushort | 16 位无符号整数类型 | 0 到 65,535 | 0 |

引用类型

引用类型不包含存储在变量中的实际数据。但它们包含对变量的引用。

即,它们指向的是一个位置,而不是实际的值。

使用多个引用类型变量时,变量可以指向同一个位置,当其中一个变量改变数据时,其他指向该位置的引用类型变量也会随着改变。

内置的引用类型: objectdynamicstring

除此之外,自定义的引用类型有: classinterfacedelegate

对象类型 Object

是 C# 通用类型系统中所有数据类型的基类。

ObjectSystem.Object 类的别名。

所以可以被分配给任何其他类型的值。

但是在分配值之前,需要先进行类型转换。

当值类型转换为对象类型时,则称之为装箱,反之,则称之为拆箱。注意,只有装过箱的数据才能拆箱。

object obj;
obj = 1000; // 装箱
动态类型 Dynamic

可以存储任何类型值在动态类型变量中,这些变量的类型检查会在运行时发生。

动态类型与对象类型相似,但对象类型在编译时类型检查。

// 语法
dynamic <variable> = value;

// 示例
dynamic example = 100;
字符串类型 String

允许给变量分配任何字符串。

StringSystem.String 的别名。是从 Object 类派生的。

可通过两种形式分配:

string str = 'string';

@'string';

// string 字符串前 可以增加 @ (逐字字符串) 将转义字符 \ 作为普通字符对待。
string str = @'C:\Windows';
// 等价
string str = 'C:\\Windows';
// 同时, @字符串中可以任意换行
string str = @"
one row
two row
three row
";

指针类型

存储另一种类型的内存地址。

C# 的指针和 C 或 C++ 中有相同功能。

// 语法
<type>* <identifier>;

// 示例
char* cptr;

可空类型

c# 提供了一个特殊的数据类型, nullable 类型。

它可以在表示基础值正常范围内的值 和 null 值。

Nullable<Int32> 读作为 可空的 Int32。

<type>? <name> = <value>;

int? i = 3;
// 等价于
Nullable<int> i = new Nullable<int>(3);

int i; // 默认 0
int? i; // 默认 null

泛型

泛型使得程序方法可以更加的广泛使用。可以更大限度的重用代码。

public class Gener<T> {
  public void Add(T input) {
    System.Console.WriteLine("input is " + input);
  }
}

class Enter {
  static void Main() {
    Gener<int> generInt = new Gener<int>();
    generInt.Add(23);

    Gener<string> generString = new Gener<string>();
    generString.Add("awd");
  }
}

匿名类型

提供了一种方便的方法,用来将一组只读属性封装到单个对象中,而无需首先显式定义类型。

类型由编译器生成。且不能在源代码级别中使用。

结合 new 操作符和对象初始值设定项创建匿名类型。

public class Lambda {
  public void outer() {
    var go = new Persion();

    var v = new { 
      name = "张三", 
      age = 18,
      go
    };
    System.Console.WriteLine(v);
  }
}

类型转换

类型转换是将一个数据类型的值转换为另一个数据类型的过程。

C# 的类型转换分两种:

  • 隐式类型转换

    只能将较小类型转换为较大类型。

  • 显示类型转换(强制类型转换)

    会导致数据丢失,或精度损失,需进行兼容性检查。

    对于对象类型的转换,需进行类型转换兼容性和安全性的检查。

隐式类型转换

隐式类型转换指的是将一个较小范围的数据类型转换为较大范围的数据类型时,编译器会自动完成类型转换

是以 C# 默认的安全方式进行的转换,不会导致数据丢失。

byte b = 10;
int i = b; // 隐式转换 无需显示转换

int i = 400;
long l = i; // 隐式转换 不会导致数据丢失

float f = 2.4;
double d = f; // 隐式转换,不会导致数据丢失
显式类型转换

即,强制类型转换。

指将一个较大范围的数据类型转换为较小范围的数据类型,或将一个对象类型转换为另一个对象类型。

这时候就需要使用强制类型转换符号进行显示转换,但是会造成数据丢失。

int i = 10;
byte b = (byte)i; // 显示转换,需使用强制类型转换符号

double d = 3.14;
int i = (int)d; // 可能会损失小数部分

int i = 40;
float f = (float)i; // 可能损失精度

int i = 123;
string s = i.ToString(); // 将 int 转换为 string
内置类型转换方法
  • ToBoolean 将可能的类型转换为布尔类型。
  • ToByte 将可能的类型转换为字节类型。
  • ToChar 将可能的类型转换为单个 unicode 字符。
  • ToDateTime 将整数或字符串类型转换为 日期-时间 结构。
  • ToDecimal 将整数或浮点数转换为十进制类型。
  • ToDouble 将可能的类型转换为双进度浮点类型。
  • ToInt16 将可能的类型转换为 16 位整数类型。
  • ToInt32 将可能的类型转换为 32 位整数类型。
  • ToInt64 将可能的类型转换为 64 位整数类型。
  • ToSbyte 将可能的类型转换为有符号字节类型。
  • ToSingle 将可能的类型转换为小浮点数类型。
  • ToString 将可能的类型转换为字符串类型。
  • ToType 将可能的类型转换为指定类型。
  • ToUInt16 将可能的类型转换为 16 位无符号整数类型。
  • ToUInt32 将可能的类型转换为 32 位无符号整数类型。
  • ToUInt64 将可能的类型转换为 64 位无符号整数类型。

变量

是用于存储和标识数据的标识符。

声明变量时,需指定变量类型,且可分配一个初始值。

正确初始化变量是良好习惯,否则可能会导致程序有时发生意外结果。

每个变量都有一个特定类型,它决定了变量的内存大小和布局。

范围内的值都可以存储在内存中,可以对变量进行操作。

C# 中的类型可大致分为几类:

  • 整数类型:
  • sbyte
  • byte
  • short
  • ushort
  • int
  • uint
  • long
  • ulong
  • char
  • 浮点类型:
  • float
  • double
  • 十进制类型:
  • decimal
  • 布尔类型:
  • 指定的 truefalse
  • 空字符串:
  • string
  • 空类型:
  • 可为空值的数据类型。

C# 允许定义其他值类型的变量。也允许定义引用类型变量。

C# 4.0 引入了动态类型,它允许运行时推断变量类型。但通常推荐静态类型以获得更好性能和编译时类型检查。

// 变量语法
<type> <variable> = <value>;

例子:

using System;
namespace VariableInit {
  class Program {
    static void Main (string[] args) {
      short s;
      int i;
      double d;

      // 初始化值
      s = 10;
      i = 20;
      d = s + i;

      Console.WriteLine("{0} + {1} = {2}", s, i, d);
      Console.ReadLine();
    }
  }
}

Lvalues 和 Rvalues

lvalue 表达式可出现在赋值语句的左边或右边。

rvalue 表达式只能出现在赋予语句右边。

变量是 lvalue 的, 所以可以出现在赋值语句的左边,而数值则是 rvalue 的。所以不能被赋值,只能出现在赋值语句右边。

int i = 20; // 有效的
10 = 20; // 无效的

常量

常量是固定值,可以是任何基本数据类型。

可以当做常规变量,但是定义后不可修改。

// 语法

// 静态常量 编译时
const <type> <name> = <value>;

// 动态常量 运行时
// 在运行时确定值,只能在声明时或构造函数中初始化
readonly <type> <name> = <value>;
整数常量

可以是十进制,八进制,十六进制,前缀指定基数:

  • 0x 或 0X 为十六进制。
  • 0 表示八进制。
  • 无前缀则是十进制。

也存在后缀,U (unsigned) 和 L (long) 。大小写任意。可任意组合。

212    // 合法
213u   // 合法
0xfel  // 合法
078    // 非法 因为 8 不是一个八进制数
0322uu // 非法 因为不能重复后缀
浮点常量

由整数,小数点,小数或指数组成。

可使用小数或指数表示浮点数。

浮点形式时,必须包含小数点,指数或同时包含。

指数形式时,必须包含整数,小数或同时包含。

有符号的指数以 e 或 E 表示。

3.14    // 合法
32E-5L  // 合法
510E    // 非法 不完全指数
210f    // 非法 无小数或指数
.e55    // 非法 缺少整数或小数
字符常量

存储在单引号中。通常是一个字符,转义序列或通用字符。

| 转义序列 | 含义 |
| :——— | :————————- |
| \\ | \ 字符 |
| ' | ' 字符 |
| " | " 字符 |
| \? | ? 字符 |
| \a | Alert 或 bell |
| \b | 退格键(Backspace) |
| \f | 换页符(Form feed) |
| \n | 换行符(Newline) |
| \r | 回车 |
| \t | 水平制表符 tab |
| \v | 垂直制表符 tab |
| \ooo | 一到三位的八进制数 |
| \xhh . . . | 一个或多个数字的十六进制数 |

字符串常量

存储在双引号或 @"" 中,包含的字符与字符常量相似。

作用域

作用域定义了变量的可见性和生命周期。

通常以花括号 {} 定义的代码块确定。

有助于管理变量可见性和生命周期,确保其在有效范围内使用。

并防止命名冲突。

全局变量

严格来讲, C# 中不存在全局变量。

但是可以通过 在一个类中实现静态属性和静态方法来达到全局变量和全局方法的作用。

public static class GlobalFunAndVar {
  public static int count = 30;
  public static string name = "global";

  public static int getCount () {
    return count;
  }

  public static void addCount () {
    count = count + 1;
  }
}

public class Example {
  public void Main (string[] args) {
    // 使用全局方法
    // 全局变量同理
    GlobalFunAndVar.getCount();
  }
}

局部变量

在方法、循环、条件语句等代码块内部定义的是局部变量,只能在声明代码块中可见。

void ExampleMethod () {
  int localVariable = 10; // 局部变量
  // todo...
}

// 方法外部 localVariable 不可见

块级作用域

C# 7 版本开始引入了块级作用域,即,任何使用 {} 创建的代码块都可以定义变量的作用域。

...
{
  int i = 20;
}

// 作用域外 i 不可见

方法参数作用域

方法的参数也有作用域,在整个方法中都可见。

void ExampleMethod (int i) {
  // i 在整个方法中可见
}

静态变量作用域

在类级别定义,仅作用域定义的类。

class Example {
  static int staticInt = 30; // 静态变量 仅此类可见
}

循环变量作用域

在循环中声明的变量在循环体内可见

for (int i = 0; i < 5; i++) {
  // i 在此循环体内可见
}

// 循环体外 i 不可见

运算符

是一种告诉编译器执行特定数学,逻辑操作的符号。

算术运算符

using System;

namespace Operator {
  class Program {
    static void Main (string[] args) {
      int a = 10;
      int b = 20;
      int c = a + b;

      Console.WriteLine("加法 {0} + {1} = {2}", a, b, a + b);
      Console.WriteLine("减法 {0} - {1} = {2}", a, b, a - b);
      Console.WriteLine("乘法 {0} * {1} = {2}", a, b, a * b);
      Console.WriteLine("除法 {0} / {1} = {2}", a, b, a / b);
      Console.WriteLine("取模 {0} % {1} = {2}", a, b, a % b);

      Console.WriteLine("\n++c 是先自增再运算 \n\n当前操作的 c = {0}", ++c);
      Console.WriteLine("操作之后的 c = {0}", c);
      Console.WriteLine("\n--c 是先自减再运算 \n\n当前操作的 c = {0}", --c);
      Console.WriteLine("操作之后的 c = {0}", c);
      Console.WriteLine("\nc++ 是先运算再自增 \n\n当前操作的 c = {0}", c++);
      Console.WriteLine("操作之后的 c = {0}", c);
      Console.WriteLine("\nc-- 是先运算再自减 \n\n当前操作的 c = {0}", c--);
      Console.WriteLine("操作之后的 c = {0}", c);
    }
  }
}

关系运算符

using System;

namespace Operator {
  class Program {
    static void Main (string[] args) {
      int a = 10;
      int b = 20;

      Console.WriteLine("大于: {0} > {1} is {2}", a, b, a > b);
      Console.WriteLine("小于: {0} < {1} is {2}", a, b, a < b);
      Console.WriteLine("大于等于: {0} >= {1} is {2}", a, b, a >= b);
      Console.WriteLine("小于等于: {0} <= {1} is {2}", a, b, a <=b);
      Console.WriteLine("相等: {0} == {1} is {2}", a, b, a == b);
      Console.WriteLine("不相等: {0} != {1} is {2}", a, b, a != b);
    }
  }
}

逻辑运算符

using System;

namespace Operator {
  class Program {
    static void Main (string[] args) {
      int a = 20;
      int b = 10;

      // 全真为真
      Console.WriteLine("逻辑与(&&): {0} > {1} && {2} < {3} 为 {4}", a, b, b, a, a > b && b < a);
      // 全假为假
      Console.WriteLine("逻辑或(||): {0} > {1} || {2} > {3} 为 {4}", a, b, b, a, a > b || b > a);
      // 取反 全真为假 全假为真
      Console.WriteLine("逻辑非(!): !({0} > {1} && {2} < {3}) 为 {4}", a, b, b, a, !(a > b && b < a));
    }
  }
}

位运算符

using System;

namespace Operator {
  class Program {
    static void Main (string[] args) {
      int a = 20;
      int b = 10;

      Console.WriteLine("{0} 二进制为: {1}, {2} 二进制为: {3}", a, "10100", b, "1010");

      Console.WriteLine("二进制和运算: {0} & {1} = {2}", a, b, a & b);
      Console.WriteLine("二进制或运算: {0} | {1} = {2}", a, b, a | b);
      Console.WriteLine("二进制异或运算: {0} ^ {1} = {2}", a, b, a ^ b);
      Console.WriteLine("二进制取反运算: ~{0} = {1}", a, ~a);
      Console.WriteLine("二进制左移运算: {0} << {1} = {2}", a, 2, a << 2);
      Console.WriteLine("二进制右移运算: {0} >> {1} = {2}", a, 2, a >> 2);
    }
  }
}

赋值运算符

using System;

namespace Operator {
  class Program {
    static void Main (string[] args) {
      int i1 = 1; // 简单赋值运算符
      i1 += 1; // 加且赋值 即 i1 = i1 + 1
      i1 -= 1; // 减且赋值 即 i1 = i1 - 1
      i1 *= 2; // 乘且赋值 即 i1 = i1 * 2
      i1 /= 2; // 除且赋值 即 i1 = i1 / 2
      i1 %= 2; // 取模且赋值 即 i1 = i1 % 2
      i1 <<= 2; // 左移且赋值 即 i1 = i1 << 2
      i1 >>= 2; // 右移且赋值 即 i1 = i1 >> 2
      i1 &= 2; // 按位与且赋值 即 i1 = i1 & 2
      i1 ^= 2; // 按位异或且赋值 即 i1 = i1 ^ 2
      i1 |= 2; // 按或且赋值 即 i1 = i1 | 2
    }
  }
}           

其他运算符

using System;

namespace Operator {

  class Example {
    static void ExampleMethod (string[] args) {
    }
  }

  class Program {
    static void Main (string[] args) {
      int a = 20;
      int b = 10;
      // null 合并运算符
      // null 合并运算符为类型转换定义了一个预设值
      // 防止了可空类型的值为 null。
      int num = null ?? 5;

      // 可空类型 因为值类型通常不能标识为空
      // <type>? <name> = null;
      int? c = null;
      // 变量为空时返回指定值
      int d = null ?? 10;
      Example e = new Example();
      // null 检查运算符
      // 如果对象为 null
      // 则不进行后续运算
      int? i = obj?.getX()?.x

      Console.WriteLine("int 的数据类型大小为: {0}", sizeof(int));
      Console.WriteLine("类 Example 的类型是: {0}", typeof(Example));
      // Console.WriteLine("变量 int a 的实际地址是: {0}", &a);
      // Console.WriteLine("变量 int a 的指针是: {0}", *a);
      Console.WriteLine("三元表达式 {0} > {1} ? 真的 : 假的 的值是 {2}", a, b, a > b ? "真的" : "假的");
      Console.WriteLine("变量 e 的类型 {0}是 Example", e is Example ? ' ' : '不');
      Console.WriteLine("强制转换操作符 as 即使失败也不会抛出错误");
    }
  }
}

运算符优先级

运算符优先级确定了表达式中项的组合,这会影响表达式的计算。

比如先乘除后加减。

优先级可以简单概括为:

有括号先括号,其次乘除再加减,接着位移再关系,逻辑之后是条件,最后一个逗号。

由于括号可以改变优先级,因此推荐使用括号明确运算顺序。

| 类别 | 运算符 | 结合性 |
| :——–: | :——————————– | :——: |
| 后缀 | () [] -> . ++ - - | 从左到右 |
| 一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
| 乘除 | * / % | 从左到右 |
| 加减 | + - | 从左到右 |
| 移位 | « » | 从左到右 |
| 关系 | < <= > >= | 从左到右 |
| 相等 | == != | 从左到右 |
| 位与 AND | & | 从左到右 |
| 位异或 XOR | | 从左到右 |
| 位或 OR | \| | 从左到右 |
| 逻辑与 AND | && | 从左到右 |
| 逻辑或 OR | \|\| | 从左到右 |
| 条件 | ?: | 从右到左 |
| 赋值 | = += -= *= /= %=»= «= &=
= \|= | 从右到左 |
| 逗号 | , | 从左到右 |

条件语句

可以对条件进行判断,根据条件是否为真进行操作。

using System;

namespace  Condition {
  class Condition {
    static void Main (string[] args) {

      int i1 = 10;
      int i2 = 20;

      if (i1 > i2) {
        Console.WriteLine("{0} 大于 {1}", i1, i2);
      }
      else if (i1 < i2) {
        Console.WriteLine("{0} 小于 {1}", i1, i2);
      }
      else {
        Console.WriteLine("{0} 等于 {1}", i1, i2);
      }

      Console.WriteLine("{0} 大于 {1} 是 {2}", i1, i2, i1 > i2 ? "真的" : "假的");

      switch (i1) {
        case 20:
          Console.WriteLine("是 20");
          break;
        default:
          Console.WriteLine("默认输出");
          break;
      }
    }
  }
}

循环语句

有时需要多次执行同段代码。可使用循环。

using System;

namespace Loop {
  class Loop {
    static void Main (string[] args) {

      int index = 0;
      int[] arr = new int[] { 0, 1, 2, 3, 4, 5};

      // 死循环
      while (true) {
        if (index == 5) {
          Console.WriteLine("循环到{0}次, 跳过这次循环", index++);
        }
        Console.WriteLine("循环第{0}次", index++);
        if (index % 2 == 0) {
          Console.WriteLine("循环{0}次, 这是个偶数次数", index);
        }
        if (index == 10) {
          Console.WriteLine("循环{0}次, 结束循环了", index);
          break;
        }
      }

      do {
        if (index == 10) {
          Console.WriteLine("循环{0}次, 结束循环了", index);
          break;
        }
        Console.WriteLine("循环第{0}次", index++);
      } while(true);

      for (int i = 0; i < 10; i++) {
        Console.WriteLine("循环第{0}次", i);
      }

      Console.WriteLine("接下来遍历 {0}", arr);
      foreach (int num in arr) {
        Console.WriteLine("遍历到的元素是 {0}", num);
      }
    }
  }
}

封装

封装被定义为 将一个或多个项目封闭在一个物理的或逻辑的包中

面向对象编程中,封装是为了防止对细节的访问,比如:司机只需要会开车而不需要知道汽车是如何制造的。

抽象和封装都是面向对象编程的特性,抽象允许了信息的可视化,封装则使得开发者只需要知道如何使用而不用考虑实现。

假设目前有 父类 A,B, 内部的 A 的子类 C,外部的 A 的子类 D。

根据具体需要,可以设置使用者的访问权限,可以通过访问修饰符实现:

  • public 公开的,所有对象都能访问。

    所有对象都知道。

  • private 私有的,只能在对象内部访问。

    只有 A 知道。

  • protected 保护的,只有在该类及子类中访问。

    ACD 可知道,B 不可知道。

  • intemal 内部的,只有在同一个程序集中的对象可以访问。

    ABC 都知道,D不知道。

  • protected intemal 保护内部的,只有在当前程序集或派生自该类的对象访问。

    ABCD 都可知道。其他人不知道。

方法

就是将一些相关的语句组成执行一个任务的语句块。

每一个 C# 程序都至少有一个带 Main 方法的类。

如果需要使用方法,那么首先需要定义方法,接着才能调用方法。

// 语法
<AccessSpecifier> <ReturnType> <MethodName> (ParameterList ) {
    // todo...
}
  • AccessSpecifier 即 访问修饰符。
  • ReturnType 返回类型,如果无返回值,则为 void
  • MethodName 方法名称,大小写敏感且不允许重复。
  • ParameterList 参数列表,是可选的。
using System;

// 工具命名空间
namespace Utils {
  // 公共工具类
  public class Utils {
    // 公开的静态的获取数字方法
    public static int getNum () {
      return -1;
    }

    // 公开的动态获取数字方法
    // 需要首先实例化才能调用
    public int dynamicGetNum () {
      return ++num;
    }
  }
}

// 程序命名空间
namespace Program {
  // 程序类
  class Program {
    // 主方法
    static void Main () {
      // 调用方法
      int one = Utils.Utils.getNum();
      // 实例化类
      Utils.Utils u = new Utils.Utils();

      Console.WriteLine("one is {0}", one);
      // 调用实例方法
      Console.WriteLine("two is {0}", u.getDynamicNum());
      Console.WriteLine("three is {0}", u.getDynamicNum());
    }
  }
}

传参

  • 实参 即实际传入的参数。
  • 形参 即定义在方法上的形式上的参数。
...
// n 为形参
static void Methods (int n) { 
    // todo...
}
...
...
// 23 为实参
Methods(23);
...
按值传参

调用时,会为每个值参数创建一个位置,然后实参会被复制给形参。形参的操作不会影响实参。

using System;

namespace ParameterMethods {
  class Program {
    static void Main () {
      int n = 20;
      int m = 30;
      Console.WriteLine("{0} + {1} = {2}", n, m, computerSum(n, m));
    }

    // 传递了两个参数
    // 分别是 整数参数 n
    // 和 整数参数 m
    // 返回结果是 n + m 的和
    static int computerSum (int n, int m) {
      return n + m;
    }
  }
}
引用传参

因为引用类型是变量的内存位置的引用,因此在传参时,实参的引用会被复制给形参。

形参的改变也会随之改变实参。

using System;

namespace ParameterMethods {
  class Program {
    static void Main () {

      int n = 20;
      int m = 30;

      Console.WriteLine("传参前 n = {0}, m = {1}", n, m);
      Console.WriteLine("{0} + {1} = {2}", n, m, computerSum(ref n, ref m));
      Console.WriteLine("传参后 n = {0}, m = {1}", n, m);
    }

    // 通过此函数 会改变传入参数变量
    // 传参时,变量必须已经初始化
    static int computerSum (ref int n, ref int m) {
      n += m;
      return n + m;
    }
  }
}
输出传参

输出参数会将方法输出的数据赋值给自己。

提供的参数变量无需赋值,当需要从参数没有指定初始值的方法中返回值时,很有用。

using System;

namespace ParameterMethods {
  class Program {
    static void Main () {

      int n = 20;
      int m = 30;

      Console.WriteLine("传参前 n = {0}, m = {1}", n, m);
      Console.WriteLine("{0} + {1} = {2}", n, m, computerSum(out n, out m));
      Console.WriteLine("传参后 n = {0}, m = {1}", n, m);
    }

    // 通过此函数 会对传入变量进行更改
    // 无法将数据传入
    static int computerSum (out int n, out int m) {
      // 函数内部必须初始化参数值
      n = 30;
      m = 50;
      return n + m;
    }
  }
}
多个参数

可以使用 params 关键字将所有参数变成一个数组的元素。

既可以传入多个实参,也可以传递一个数组实参。

需要注意的是,在此关键字后,不允许再有任何形参。

且不能与 refout 共用。

using System;

namespace StudyArray {
  class StudyArray {

    static int addElement (params int[] arr) {
      int sum = 0;
      foreach (int el in arr) {
        sum += el;
        Console.WriteLine("element is {0}", el);
      }

      return sum;
    }

    static void Main () {
      Console.WriteLine("sum is {0}", addElement(10,15,25,50));
    }
  }
}

模式匹配

是一种测试表达式是否具有某种特定特征的方法。

is 表达式仅支持通过模式匹配测试表达式并有条件的声明结果。

using System;

public class Entry
{
  static void Main()
  {
    string? args = "this is not null";
    // 类型测试 测试变量是否与类型匹配
    // if (args is string)
    // 声明模式 测试变量类型并分配给新变量
    // if (args is string str)
    // 常数模式与 关键字 null 匹配
    // not 取反
    if (args is not null)
    {
      Console.WriteLine("maybe is exists, value is " + args);
    }
    else
    {
      Console.WriteLine("maybe is not exists, value is " + args);
    }
  }
}

弃元

是一种人为取消使用的占位符变量。

有时可能会获取到一些不会使用的值。这个时候就可以时候弃元。

// 比如 GetInfo() 方法返回一个包含三个元素的元组。但是只需要第三个元素。
(_, _, area) = GetInfo(); 

out 参数使用

可使用占位符标识单个 out 的值,当使用 out 参数调用方法时,也可使用弃元标识 out 参数值。

独立弃元

用于指示需要忽略的任何变量。

// 强制赋值 参数 null 时抛出错误
_ = args ?? throw new Error;

数组 Array

是一个存储相同类型元素的固定大小的顺序集合。

通常认为是一个同一类型变量的集合。

<type>[] <name>;

  • type 指定存储数组中的数据类型

  • [] 指定数组维度和数组大小

  • 二维 int[,] array

    // 初始化
    int [,] arr = new int [2,2] {
      { 0, 1 }, // 索引为 0
      { 2, 3 }, // 索引为 1
    }
    
    // 访问
    arr[0][0]; // 0
    arr[0][1]; // 1
    arr[1][0]; // 2
    arr[1][1]; // 3
    
  • 三维 int[ , , ] array

    // 初始化
    int [,] arr = new int [ 2, 2, 2 ] {
      {
        { 0, 1 },
        { 2, 3 }
      },
      {
        { 4, 5 },
        { 6, 7 }
      }
    }
    
    // 访问
    arr[0][0][0]; // 0
    arr[0][0][1]; // 1
    arr[0][1][0]; // 2
    arr[0][1][1]; // 3
    arr[1][0][0]; // 4
    arr[1][0][1]; // 5
    arr[1][1][0]; // 6
    arr[1][1][1]; // 7
    
  • name 数组名称。

声明数组时,不会初始化数组,数组是引用类型,因此需要使用 new 创建实例。

创建数组时,编译器会隐式初始化每一个数组元素为默认值。比如 int[] 元素会被初始化为 0。

double[] array = new double[10];

// 赋值给数组
array[0] = 2.3;

// 初始化并赋值
int[] intArray = new int[5] = { 1, 2,  3,  4, 5 };
// 可以省略数组大小
int[] intArray = new int[] = { 1, 2,  3,  4, 5 };

// 数组元素通过索引访问
// 索引是从零开始的
intArray[2] // 3

交错数组

是数组的数组,是一维数组。

与二维数组的区别在于,交错数组的每一行长度都可以是不一样的。

<type> [][] <name>;

int [][] arr;

int[][] arr = new int[5][];
for (int i = 0; i < arr.Length; i++) {
    arr[i] = new int[4];
}

元组

定义元组,需要首先指定所有成员类型,或指定字段名称。

不可以在元组类型中定义方法,但可以使用 .NET 提供方法。

元组是值类型的。支持相等运算符。

(double, int, string) = (3.14, 5, '6');
// 或使用 var 关键字自动推导类型
var t = (1, 2, 3, 4, 5, 6, 7);

// 显示定义元组字段名称
var t = (max: 10, min: 0);

// 如果未指定,则默认为变量名称
var max = 10;
var min = 0;
var t = (max, min); // t.max = 10, t.min = 0

// 如果变量名称是元组类型的成员名称
// 或 另一元组的显示,隐式字段的重复项
// 则不会成为元组字段

// 元组常用于返回类型
// ...
(int min, int max) FindMinAndMax (int[] input) {
  // ...
  return (min, max);
}
// ...

析构元组

C# 提供了内置的元组析构支持。

可在单个操作中解包元组元素,语法与定义元组的语法类似。

将要分配的元素变量放在赋值语句左侧。

var (_, _, name) = getInfo(); // 通过弃元放弃了前两位元素

字符串 String

可以创建字符串的方法:

  • string 变量指定字符串。
  • 使用 String 类构造函数。
  • 使用字符串串联运算符 +。
  • 检索属性或调用一个返回字符串的方法。
  • 格式化方法来转换一个值或对象为它的字符串表达形式。
using System;

namespace StudyString {
  class StudyString {
    static void Main () {
      char firstName = '张';
      char lastName = '三';
      string fullName = firstName.ToString() + lastName.ToString();
      char[] letters = { 'h', 'e', 'l', 'l', 'o' };
      string str = "";

      foreach (char c in letters) {
        str += c.ToString();
      }

      Console.WriteLine("{0}, 姓{1} 名{2}, 全名 {3}", fullName, firstName,lastName,fullName);
      Console.WriteLine(str);

      if (fullName.Contains(firstName.ToString())) {
        Console.WriteLine(fullName + "中存在有字符: " + firstName);
      }

      Console.WriteLine("在字符串 {0} 中截取子串 {1}", str, str.Substring(2));
    }
  }
}

结构体 Struct

c# 中,结构体是值类型的数据结构。

使得一个单一变量可以存储各类型的数据。

使用 struct 关键字创建结构体。

结构体特点

与传统的 C 或 C++ 不同,C# 的结构:

  • 可带方法,字段,索引,属性,运算符方法和事件。
  • 可定义构造函数,但不能定义析构函数,无参构造函数是默认自动定义的,因此只能定义有参构造函数。且不能被改变。
  • 与类不同,无法继承其他结构或类。
  • 不能作为其他结构或类的基础结构。
  • 可实现一个及以上接口。
  • 结构成员不能指定为 abstractvirtualprotected
  • 当使用 new 操作符时,会调用适当的构造函数来创建结构。
  • 也可以不使用 new 操作符进行实例化。但只有所有字段初始化之后,才能赋值,对象才被使用。

类 和 结构

类和结构的不同点:

  • 类是引用类型的,而结构则是值类型。
  • 结构不能继承。
  • 结构不能声明默认的构造函数。
  • 结构体中声明的字段无法赋予初始值。而类可以。
  • 结构体分配在栈中,类分配在堆中。
using System;
using System.Text;

struct Books {
  private int id;
  private int price;
  private string title;
  private string author;

  public void setValues(int i, int p, string t, string a) {
    title = t;
    id = i;
    price = p;
    author = a;
  }

  public void displayInfo () {
    Console.WriteLine("book id is: " + id);
    Console.WriteLine("book title is: " + title);
    Console.WriteLine("book author is: " + author);
    Console.WriteLine("book price is: " + price);
  }
}

namespace StudyStruct {
  class StudyStruct {
    static void Main () {
      Books book1 = new Books();
      Books book2 = new Books();

      book1.setValues(1, 23, "语文书", "编辑组");
      book2.setValues(2, 28, "英语书", "编辑组");

      Console.WriteLine("------------------------------");
      book1.displayInfo();
      Console.WriteLine("------------------------------");
      book2.displayInfo();
      Console.WriteLine("------------------------------");
    }
  }
}

枚举

是一组命名整型常量,以 enum 关键字声明。

C# 枚举是值类型,因此,枚举包含自己的值,且不能继承和传递继承。

枚举中的每个符号都是一个比前面符号大的整数,默认第一个符号值为 0。

也可以自己指定每个符号的值。

枚举可以在类中,类外部声明。

enum ExampleEnum {
    One, // 0
  Two, // 1
  Three // 2
}

enum ExampleEnum {
  One = 3,
  Two = 1,
  Three = 0
}

位标志枚举

以一周七天为例,可任意进行各天的组合。

使用位标识枚举时,可使用或运算创建新组合。

为了让位标识枚举值能够组合,所有值都应是 2 的幂。

简单枚举中,可让名称 None 或其他常用默认名称对应 0,。

但是标识枚举要求 0 对应 None。

[Flags]
enum Weeks {
  None = 0,
  Sun = 0x01,       // 0000 0001
  Mon = 0x02,       // 0000 0010
  Tue = 0x04,       // 0000 0100
  Wed = 0x08,       // 0000 1000
  Thu = 0x10,       // 0001 0000
  Fri = 0x20,       // 0010 0000
  Sat = 0x40,       // 0100 0000

  WeekEnd = Sun | Sat,      // 0100 0001
  WorkDay = Mon | Tue | Wed | Thu | Fri     // 0011 1110
}

在面向对象编程中,类是对于事物属性的抽象化。

比如人类,人类有说话功能,有双脚走路功能,有名字属性,有性别属性等。

  • this 关键字在类中指代为当前实例。 也可以作为扩展方法的第一个参数的修饰符。

    class ThisKeyword {
    public void ThisKeyword () {
      this; // 就是 ThisKeyword 这个类的实例
      this.say('init'); // 调用实例方法
    }
    public void say (string message) {
      Console.WriteLine(message);
    }
    }
    
    class Enter {
    public void Main () {
      ThisKeyword example = new ThisKeyword();
      // 此时 example 等价与 在类中的 this
      example.say("created");
    }
    }
    
  • base 关键字允许从子类中访问父类的成员。

  • 调用父类上已由其他方法重写的方法。

  • 指定创建子类实例时应调用的父类构造函数。

类的定义

using System;

// 命名空间 地球
namespace Earth {
  // 人类
  public class People {
    // 公开的性别 一般都能一眼看出来
    public bool sex = false;
    // 受保护的名称 只有认识的人才能知道
    protected string name;
    // 受保护的年龄 只有认识的人才能知道
    protected int age = 18;
    // 私密的想法 只有自己知道
    private string want = "i want doing to ...";

    static void Main () {

    }

    // 私有的说话功能 只有自己想说话才能说话
    private void say (string say) {
      Console.WriteLine(say);
    }
  }
}

构造函数

是类的一个特殊成员函数。

默认情况下编译器会在没有显示编写时提供一个默认的无参,无方法体的构造函数。

构造函数名称与类名称是完全相同的。没有任何返回类型。

默认的构造函数是无参的。但也可以根据需要传参。

namespace StudyClass {
  class StudyClass {
    // 构造函数
    public StudyClass (int id) {
      Console.WriteLine("id is " + id);
    }

    // 程序入口 Main 
    static void Main () {
      StudyClass c = new StudyClass(123);
    }
  }
}

析构函数

是一个特殊的成员函数。

当类的对象超出范围时执行。

名称是在类名称前增加一个 ~ 符号。

不返回值,也不带任何参数。

用于在结束程序之前释放资源等。

不能被继承或重载。

namespace StudyClass {
  class StudyClass {
    public StudyClass (int id) {
      Console.WriteLine("id is " + id);
    }

    ~StudyClass () {
      Console.WriteLine("Object StudyClass is removed");
    }

    static void Main () {
      StudyClass c = new StudyClass(123);
    }
  }
}

静态成员

使用 static 关键字创建。

声明一个静态成员时,无论有多少个类的实例创建,都只有一个该成员。

当类成员函数声明为 public static 时,无需实例化类即可调用。

继承

继承是面向对象中的重点之一,它允许了在一个类的基础上定义另一个类。

当有一个定义在类中的方法需要在子类中实现时,使用关键字 virtual 定义为虚方法。

同时,虚方法必须要有实现,哪怕只是一对花括号。

虚方法可以在不同的子类中有不同实现。

对虚方法的调用是在运行时发生。

在子类中重写虚方法,需要使用关键字 override 扩展重写。且只能重写虚方法。

如果父类和子类有同名方法,则使用 new 关键字定义。

nnamespace StudyClass {

  class Enter {
    static void Main () {
      StudyChildClass child = new StudyChildClass(1231);

      child.say();
      child.go();
      child.say();
    }
  }

  class StudyClass {
    public StudyClass (int id) {
      Console.WriteLine("id is " + id);
    }

    ~StudyClass () {
      Console.WriteLine("Object StudyClass is removed");
    }

    // virtual 关键字用于声明并表示此为可重写的
    public virtual void say () {
      Console.WriteLine("say");
    }

    public void sleep () {
      Console.WriteLine("sleep");
    }
  }

  // 多重继承则是 class child: father, monther { ... }
  class StudyChildClass: StudyClass {

    public StudyChildClass (int id): base(id) {
      Console.WriteLine("Child init");
    }

    // override 关键字用于扩展或修改继承的方法属性
    public override void say () {
      base.say();
      Console.WriteLine("child say");
    }

    public void go () {
      Console.WriteLine("go");
    }

    public new void sleep () {
      Console.WriteLine("go to bed");
    }
  }
}

多态

多态指一个行为有多个表现。

面向对象中,常表现为 一个接口,多个功能。

多态可以是静态,动态的。

C# 中,每个类型都是多态的。

静态多态

编译时。

函数重载

即,一个方法,可以根据不同的参数进行不同的操作。

using System;

namespace StudyPolymorphic {
  class Print {
    public static void print (string arg) {
      Console.WriteLine("打印字符串: " + arg);
    }

    public static void print (int arg) {
      Console.WriteLine("打印数字: " + arg);
    }

    public static void print (float arg) {
      Console.WriteLine("打印小数: " + arg);
    }

    static void Main () {
      print((int)23);
      print((string)"awdawd");
      print((float)23.2);
    }
  }
}
运算符重载

可以重新定义或重载 C# 内置运算符。

重载运算符是具有特殊名称的函数,通过关键字 operator 和运算符后面的符号定义。

using System;

namespace StudyOperatorOverride {

  class Enter {
    static void Main () {
      Console.WriteLine("enter");

      Example e1 = new Example(2);
      Example e2 = new Example(3);
      Example e3 = new Example(0);

      Console.WriteLine("e1.quantity = " + e1.quantity);
      Console.WriteLine("e2.quantity = " + e2.quantity);
      Console.WriteLine("e3.quantity = " + e3.quantity);

      e3 = e1 + e2;

      Console.WriteLine("e1.quantity = " + e1.quantity);
      Console.WriteLine("e2.quantity = " + e2.quantity);
      Console.WriteLine("e3.quantity = " + e3.quantity);

    }
  }

  class Example {
    public int quantity;

    public Example (int q) {
      this.quantity = q;
    }

    // 重载了 + 运算符
    // 当两个 Example 实例相加时调用
    public static Example operator + (Example e1, Example e2) {
      Example e = new Example(0);
      e.quantity = e1.quantity + e2.quantity;
      return e;
    }

    // 重载了 == 运算符
    public static bool operator == (Example e1, Example e2) {
      bool result = false;
      if (e1.quantity == e2.quantity) {
        result = true;
      }
      return result;
    }
  }
}

| 运算符 | 描述 |
| :———————————— | :——————————————- |
| +, -, !, ~, ++, – | 这些一元运算符只有一个操作数,且可以被重载。 |
| +, -, , /, % | 这些二元运算符带有两个操作数,且可以被重载。 |
| ==, !=, <, >, <=, >= | 这些比较运算符可以被重载。 |
| &&, \|\| | 这些条件逻辑运算符不能被直接重载。 |
| +=, -=,
=, /=, %= | 这些赋值运算符不能被重载。 |
| =, ., ?:, ->, new, is, sizeof, typeof | 这些运算符不能被重载。 |

动态多态

运行时。

使用关键字 abstract 创建抽象类,以提供接口的部分类的实现。

当一个子类继承自该抽象类时,实现完成。

不能创建抽象类实例。

不能在抽象类外部声明抽象方法。

通过在类定义前使用关键字 sealed 即可将类声明为密封类。无法被继承。因此抽象类不能声明为密封类。

using System;

namespace StudyPolymorphic {
  class Enter {
    static void Main () {
      Print p = new Print();
      p.print("awd");
    }
  }

  abstract class APrint {
    abstract public void print(string args);
  }

  class Print: APrint {
    public override void print(string args) {
      Console.WriteLine("print args is " + args);
    }
  }
}

接口

接口定义了所有类继承接口时,应该遵循的语法。

接口和抽象类的区别在于,接口用于规范,抽象类用于共性。

接口定义了是什么,而继承的子类则定义了做什么

接口可以定义成员为属性,方法,事件。

接口只能包含成员声明。实现则是由子类完成。

接口使得继承的类或结构在形式上的一致。

接口不能以 publicabstract 等修饰。

接口不能存在字段变量,构造函数。

接口实现必须和接口格式一致。

接口的所有方法都必须实现。

接口使用 interface 关键字定义。

using System;

// 通常接口以 I 字母开头以表示是一个接口
interface IStudy {
  void Methods();
}

namespace StudyInterface {
  class Enter {
    static void Main () {
      Example e = new Example();
      e.Methods();
    }
  }

  class Example: IStudy {
    public void Methods () {
      Console.WriteLine("called!");
    }
  }
}

命名空间

为了区分相同的名称。

比如某公司有两个人叫张三,有事需要通知张三时,那么会起到冲突。

此时,就可以通过所在部门来区分,比如通知后勤部门的张三,运营部门的张三。

这个所在部门,就是命名空间。

命名空间以 namespace 关键字声明。

如果要使用命名空间,则需要使用 using 关键字。

// 使用命名空间 System
using System;
// 无需指定类型名称即可访问静态成员类型
using static System.Math;
// 起别名
using s = System;

// 建立命名空间 Program
namespace Program {
  // 命名空间是可以嵌套的
  namespace Display {}
}

预处理器指令

用于指导编译器在实际编译之前对信息进行预处理。

所有的预处理指令都以 # 符号开头。且必须在同一行上。

只有空白字符可以出现在预处理指令前。

预处理指令不是指令,因此不需要分号结尾。

  • #define 定义一系列成为标识符的字符。

    #deine PI
    
  • #undef 取消定义标识符。

    #undef PI
    
  • #if 测试符号是否为真。

    #if (A && B)
    // todo...
    
  • #elif 用于创建条件指令。

    #elif (A)
    // todo...
    
  • #else 用于创建条件指令。

    #else
    // todo...
    
  • #endif 结束条件指令。

    #endif
    // end if todo...
    
  • #line 修改编译器行数及输出错误和警告文件名。

    // 不推荐使用
    #line 111 "Core.cs" // 错误其实是发生在 Core.cs 的 111 行
    #line default // 还原默认
    
  • #error 指定位置生成错误。

    #error "program runing to this trigger one error"
    
  • #warning 允许从代码指定位置生成警告。

    #warning "program runing to this trigger one warning"
    
  • #region 在使用 Visual Studio Code Editor 大纲特性时,指定一个可折叠的代码块。

    不影响编译。只是在编辑器中识别。

    #region Math Handling
    // todo math handling
    #endregion
    
  • #endregion 结束 #region

  • #pragma 抑制和还原指定编译警告。可在类和方法级别执行。

    #pragma warning disable 169 // 取消编号 169 警告
    #pragma warning restore 169 // 恢复编号 169 警告
    
#define PI
#define flag
using System;

class Program {
  static void Main () {
    #if (PI && flag)
      // PI flag 都定义 编译
      Console.WriteLine("PI and flag defined");
    #elif (PI) 
      // PI 定义 编译
      Console.WriteLine("PI defined");
    #elif (flag) 
      // flag 定义 编译
      Console.WriteLine("flag defined");
    #else
      // PI flag 都没定义 编译
      Console.WriteLine("PI And flag not define");
    #endif
      Console.WriteLine("end judge");
  }
}

异常处理

异常就是程序执行期间可能会出现的问题。

  • try 关键字标识一个可能会触发异常的代码块。
  • catch 关键字标识一个捕获对应异常并处理的代码块。
  • finally 关键字标识一个不论是否发生异常处理,都会执行的代码块。
  • throw 关键字用于抛出一个异常。
using System;

class Program {
  static void Main () {
    try {
      Console.WriteLine("\n可能会抛出异常的代码\n");
      for (int i = 0; i < 10; i++) {
        Console.WriteLine(20 / i);
      }
    }
    catch (DivideByZeroException e) {
      Console.WriteLine("\n处理除以零时发生的错误。\n错误内容: \n {0}\n", e);
    }
    finally {
      Console.WriteLine("\n无论是否抛出异常,都会执行\n");
    }
  }
}

创建异常

自定义的异常类的父类是 System.ApplicationException

using System;

class CustomError: ApplicationException {
  public CustomError(string message): base(message) {}
}

class Program {
  static void Main () {
    try {
      Console.WriteLine("\n可能会抛出异常的代码\n");
      // for (int i = 0; i < 10; i++) {
      //   Console.WriteLine(20 / i);
      // }
      throw (new CustomError("this is custom error."));
    }
    catch (CustomError e) {
      Console.WriteLine("\n处理除以零时发生的错误。\n错误内容: \n {0}\n", e);
    }
    finally {
      Console.WriteLine("\n无论是否抛出异常,都会执行\n");
    }
  }
}

文件操作

文件是存储在磁盘中带有指定名称和目录路径的数据集合。

打开文件进行读写时,它变成了一个流。

如果需要进行文件操作,则需要通过 C# 的 IO 类。

| I/O 类 | 描述 |
| :————- | :——————————— |
| BinaryReader | 从二进制流读取原始数据。 |
| BinaryWriter | 以二进制格式写入原始数据。 |
| BufferedStream | 字节流的临时存储。 |
| Directory | 有助于操作目录结构。 |
| DirectoryInfo | 用于对目录执行操作。 |
| DriveInfo | 提供驱动器的信息。 |
| File | 有助于处理文件。 |
| FileInfo | 用于对文件执行操作。 |
| FileStream | 用于文件中任何位置的读写。 |
| MemoryStream | 用于随机访问存储在内存中的数据流。 |
| Path | 对路径信息执行操作。 |
| StreamReader | 用于从字节流中读取字符。 |
| StreamWriter | 用于向一个流中写入字符。 |
| StringReader | 用于读取字符串缓冲区。 |
| StringWriter | 用于写入字符串缓冲区。 |


C# 入门基础
http://localhost:8080/archives/2f385201-5bbd-439a-9b4d-6b4121af1263
作者
inksha
发布于
2024年09月14日
许可协议