数据类型

在 C# 中,变量分为以下类型:

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

值类型

值类型复制时直接拷贝。如果值类型的数据成员中包括引用类型时,引用类型只会浅拷贝。

T? 是一个可以为 T 或者null 的值类型,类似 optional

简单类型

C# 内置了下面几种类型,也叫做简单类型:

对于简单类型,支持下面的操作:

  • 支持字面量声明值,如 ‘A’ 是 char 类型的字面量
  • 可以用 const 声明简单类型常量,其它的类型不行
  • 简单类型的常量表达式在编译期执行

C# 的 char 类型是 Unicode UTF-16 ,每个 char 大小为 16 bit

枚举类型

1
2
3
4
5
6
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter
}

也可以指定枚举成员的具体类型

1
2
3
4
5
6
enum Season : ushort {
    Spring,
    Summer,
    Autumn,
    Winter
}

更多请看:枚举类型

结构体类型

结构体是值类型,可以有数据和方法, 相关文档:结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public override string ToString() => $"({X}, {Y})";
}

readonly 结构体,结构体的所有成员是不可变的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString() => $"({X}, {Y})";
}

方法也可以称为 readonly 的, 重载的方法也可加 readonly

1
2
3
4
5
6
public readonly double Sum()
{
    return X + Y;
}

public readonly override string ToString() => $"({X}, {Y})";

Tuple 类型

相关文档:Tuple 类型

1
2
3
4
5
6
7
8
9
(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

Nullable 类型

类型Optional, 值可以为 null ,相关文档:Nullable

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
double? pi = 3.14;
char? letter = 'a';

int m2 = 10;
int? m = m2;

bool? flag = null;

// An array of a nullable value type:
int?[] arr = new int?[10];

可以用 is 或者 hasValue 检测值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int? a = 42;
if (a is int valueOfA)
{
    Console.WriteLine($"a is {valueOfA}");
}
else
{
    Console.WriteLine("a does not have a value");
}
// Output:
// a is 42

int? b = 10;
if (b.HasValue)
{
    Console.WriteLine($"b is {b.Value}");
}
else
{
    Console.WriteLine("b does not have a value");
}
// Output:
// b is 10

引用类型

引用类型的存储数据的引用,赋值时执行浅拷贝。

C# 中可以声明引用类型的关键字:class, interface, delegate, record

使用 class 关键字声明类:

1
2
3
4
5
class TestClass
{
    // Methods, properties, fields, events, delegates
    // and nested classes go here.
}

使用 interface 定义接口约束:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface ISampleInterface
{
    void SampleMethod();
}

class ImplementationClass : ISampleInterface
{
    // Explicit interface member implementation:
    void ISampleInterface.SampleMethod()
    {
        // Method implementation.
    }
}

使用 record 定义一个引用类型,编译器会为它提供一些默认的机制,如相等比较是值相等,定义输出格式。

1
2
3
4
5
public record Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
};

内置 object 类型

objectSystem.Object 的别名,在 C# 的类型系统中,所有类型都直接或间接继承自 object

当一个值类型转换成 object 类型,叫装箱,当一个 object 类型的值转成值类型,叫拆箱。

string 类型

string 类型是 System.String 的别名,string 是引用类型,但是它的 \=\= 和 !\= 操作是比较值

1
2
3
4
5
6
string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

string 对象内的字符串是不可变的,用 + 会创建新的 string 对象。

1
string a = "good " + "morning";

[] 操作符可以访问字符串中的字符。

1
2
string str = "test";
char x = str[2];  // x = 's';

字符串字面量前加 @ 就是原生字符串,内部可以不用转义。

1
@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

如果字符串内部有双引号,用两个双引号表示。

1
@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

delegate 类型

delegate 类型的声明类似方法签名,它有返回值和任意数量的参数。

1
2
public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

一个 delegate 是引用类型,可以用来表示命名或者匿名方法,类似 C++ 中的函数指针。

dynamic 类型

dynamic 类型的变量可以绕过编译期类型检查,而在运行时解析。

指针类型

指针类型只能在 unsafe 代码中使用。

1
2
type* identifier;
void* identifier; //allowed but not recommended

控制流程

条件判断

使用 if-else 语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Console.Write("Enter a character: ");
char ch = (char)Console.Read();

if (Char.IsUpper(ch))
{
    Console.WriteLine("The character is an uppercase letter.");
}
else if (Char.IsLower(ch))
{
    Console.WriteLine("The character is a lowercase letter.");
}
else if (Char.IsDigit(ch))
{
    Console.WriteLine("The character is a number.");
}
else
{
    Console.WriteLine("The character is not alphanumeric.");
}

//Sample Input and Output:
//Enter a character: E
//The character is an uppercase letter.

//Enter a character: e
//The character is a lowercase letter.

//Enter a character: 4
//The character is a number.

//Enter a character: =
//The character is not alphanumeric.

可以使用 switch 语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
switch (caseSwitch)
     {
         case 1:
             Console.WriteLine("Case 1");
             break;
         case 2:
             Console.WriteLine("Case 2");
             break;
         default:
             Console.WriteLine("Default case");
             break;
     }

循环语句

for 语句

1
2
3
4
5
6
for (int i = 0; i < 3; i++)
{
    Console.Write(i);
}
// Output:
// 012

foreach 语句

1
2
3
4
5
6
7
var fibNumbers = new List<int> { 0, 1, 1, 2, 3, 5, 8, 13 };
foreach (int element in fibNumbers)
{
    Console.Write($"{element} ");
}
// Output:
// 0 1 1 2 3 5 8 13

await foreach 语句,消费异步数据流

1
2
3
4
await foreach (var item in GenerateSequenceAsync())
{
    Console.WriteLine(item);
}

使用 var 在编译器推导类型。

do while 语句

1
2
3
4
5
6
7
8
int n = 0;
do
{
    Console.Write(n);
    n++;
} while (n < 5);
// Output:
// 01234

while 语句

1
2
3
4
5
6
7
8
int n = 0;
while (n < 5)
{
    Console.Write(n);
    n++;
}
// Output:
// 01234

跳转语句

break 跳出最近的循环或 switch 语句。

1
2
3
4
5
6
7
8
for (int i = 1; i <= 100; i++)
{
    if (i == 5)
    {
        break;
    }
    Console.WriteLine(i);
}

continue 马上下一次迭代。

1
2
3
4
5
6
7
8
for (int i = 1; i <= 10; i++)
{
    if (i < 9)
    {
        continue;
    }
    Console.WriteLine(i);
}

goto 语句跳转到一个标签:

1
2
3
goto Found;

Found:

return 终止方法执行,如果在 try 块里,会执行 finally 块 。

异常语句

throw 抛出异常,终止当前程序执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using System;

namespace Throw2
{
public class NumberGenerator
{
   int[] numbers = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };

   public int GetNumber(int index)
   {
      if (index < 0 || index >= numbers.Length)
      {
         throw new IndexOutOfRangeException();
      }
      return numbers[index];
   }
}

throw 可以作为表达式

1
2
string arg = args.Length >= 1 ? args[0] :
                              throw new ArgumentException("You must supply an argument");

异常可以通过 throw 重新抛出

1
2
3
4
5
6
7
8
try
{
    return Value[0];
}
catch (NullReferenceException e)
{
    throw;
}

try-catch-finally 捕获异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class EHClass
{
    void ReadFile(int index)
    {
        // To run this code, substitute a valid path from your local machine
        string path = @"c:\users\public\test.txt";
        System.IO.StreamReader file = new System.IO.StreamReader(path);
        char[] buffer = new char[10];
        try
        {
            file.ReadBlock(buffer, index, buffer.Length);
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
        }
        finally
        {
            if (file != null)
            {
                file.Close();
            }
        }
        // Do something with buffer...
    }
}

其它知识

region 和 endregion

region 和 endregion 表示一块区域,在 VS 可以折叠起来,方便查看。