C# 基础

1. 方法

在 C# 中,方法定义的格式如下:

[访问修饰符] <返回值类型> <方法名> ([参数列表])
{
    // 方法体;
}

“[ ]”中括号意为选填项。

下面是对上述语法的解释:

  • [访问修饰符]:指定方法的访问级别,可以是publicprivateprotectedinternal等。访问修饰符决定了方法可以被哪些其他代码访问。

    • public:公共访问修饰符表示成员可以从任何地方访问,没有访问限制。

    • private:私有访问修饰符表示成员只能在声明它们的类或结构中访问。私有成员对于类外部是不可见的。

    • protected:受保护访问修饰符表示成员只能在声明它们的类、结构或派生类中访问。受保护成员对于类外部是不可见的。

    • internal:内部访问修饰符表示成员只能在同一程序集内的类、结构和方法中访问。程序集是指一组相关的代码文件,可以作为一个单元进行编译和部署。

    • protected internal:受保护内部访问修饰符表示成员可以在同一程序集内以及派生类中访问。它是protectedinternal两个访问修饰符的组合。

    • 这些访问修饰符可以应用于类、结构、方法、属性和字段。对于类和结构,访问修饰符用于控制对类型的访问级别。对于成员(方法、属性和字段),访问修饰符用于控制对成员的访问级别。

  • <返回值类型>:指定方法的返回值类型,可以是任何有效的C#类型,或者void表示方法不返回任何值。

  • <方法名>:给方法指定一个唯一的名称,用于在代码中引用该方法。

  • [参数列表]:在括号内指定方法的参数,每个参数由参数类型和参数名称组成。参数之间使用逗号分隔。

下面是一个示例,展示了如何定义一个简单的方法:

public int AddNumbers(int a, int b)
{
    int sum = a + b;
    return sum;
}

在上述示例中,方法名为AddNumbers,返回类型为int,参数列表包含两个整型参数ab。方法的访问修饰符为public,表示该方法可以从任何其他类中访问。方法体中执行了一个加法操作,将参数ab相加,并将结果存储在sum变量中,然后使用return语句将结果返回。

可以通过以下方式调用上述方法:

int result = AddNumbers(3, 4);
Console.WriteLine(result); // 输出:7

2. 变量

在 C# 中使用下述语法声明变量:

// <数据类型> <变量名>;
int i;

下面是对上述语法的解释:

  • <数据类型>:指定变量的数据类型,表示变量可以存储的值的类型。数据类型可以是C#内置的类型,如intdoublestring等,也可以是自定义的类型。

  • <变量名>:给变量指定一个唯一的名称,用于在代码中引用该变量。

除了只声明变量,还可以同时进行初始化。初始化为变量赋予一个初始值,遵循以下语法:

<数据类型> <变量名> = <初始值>;
int age = 25; // 定义一个名为 "age" 的整型变量,并将其初始化为 25
double salary = 5000.50; // 定义一个名为 "salary" 的双精度浮点型变量,并将其初始化为 5000.50
string name = "John"; // 定义一个名为 "name" 的字符串类型变量,并将其初始化为 "John"
bool isStudent = true; // 定义一个名为 "isStudent" 的布尔型变量,并将其初始化为 true

需要注意的是,变量在使用之前必须先进行定义和初始化。如果在使用变量之前没有进行定义或初始化,将会导致编译错误。

2.1 初始化变量

变量的初始化是 C# 强调安全性的另一个例子。简单地说,C# 编译器需要用某个初始值对变量进行初始化,之后才能在操作中引用该变量。

C# 有两个方法可确保变量在使用前进行了初始化:

  • 变量是类或结构中的字段,如果没有显示初始化,则创建这些变量时,其默认值就是0。

  • 方法的局部变量必须在代码中显式初始化,之后才能在语句中使用它们的值。此时,初始化不是在声明该变量时进行的,但编译器会通过方法检查所有可能的路径,如果检测到局部变量在初始化之前就使用了其值,就会标记为错误。

错误示范:

public static int ErrorInitializationVariableExample()
{
    int d;
    Console.WriteLine(d); // Local variable 'd' might not be initialized before accessing, 局部变量“d”在访问前可能没有初始化。
    return 0;
}

// 考虑下面语句:
T t; // Error example
// 在 C# 中,这行代码仅会为 T 对象创建一个引用,但这个引用还没有指向任何对象。对该变量调用方法或属性会导致错误。
// 在 C# 中实例化一个引用对象,需要使用 new 关键字。如上所述,创建一个引用,使用 new 关键字把该引用指向存储在堆上的一个对象:
T t = new T();

2.2 类型推断

在C#中,类型推断是指编译器根据上下文和赋值语句中的表达式自动推断变量的类型,而无需显式指定类型。这样可以简化代码,并提高开发效率。

类型推断使用 var 关键字。使用 var 关键字代替实际的类型。编辑器可以根据变量的初始化值“推断”变量的类型。

// 例如:
var number = 0;
// 相当于:
int number = 0;

使用类型推断关键字 var 需要遵循以下一些规则:

  • 变量必须初始化。否则,编译器就没有推断变量类型的依据。

  • 初始化器不能为空。

  • 初始化器必须放在表达式中。

  • 不能把初始化器设置为一个对象,除非在初始化器中创建了一个新对象。

声明了变量且推断出类型后,就不能再改变变量的类型了。变量的类型确定后,对该变量进行任何赋值时,其强类型化规则必须以推断出的类型为基础。

类型推断的底层原理是编译器在进行语法分析和类型检查时,通过分析表达式的结构、字面量和方法调用等信息,确定变量的类型。编译器会尽可能推断最具体的类型,以保持代码的安全性和正确性。类型推断在编译时进行,生成的中间语言(IL)中会包含实际的类型信息。

需要注意的是,虽然类型推断可以简化代码,但过度使用类型推断可能会导致代码可读性下降。适当地使用类型推断可以提高代码的简洁性和可维护性,但在某些情况下,显式指定类型可能更加清晰明确。

2.3 变量的作用域

当你在C#中声明一个变量时,它的作用域决定了该变量在程序中的可见性和可访问性。作用域是指变量在程序的不同部分中可见的范围。在C#中,变量的作用域可以是以下几种类型:

2.3.1 局部作用域(Local Scope):

局部变量在声明它的代码块内可见。代码块是由一对花括号({})括起来的代码段,例如在方法、循环或条件语句中。当你声明一个变量时,它只能在当前的代码块中使用,超出该代码块范围时将不再可见。

void MyMethod()
{
    int x = 10; // x 是局部变量,在 MyMethod 方法中可见

    if (x > 5)
    {
        int y = 20; // y 是局部变量,在 if 代码块中可见
        Console.WriteLine(x + y); // 可以访问 x 和 y
    }

    Console.WriteLine(x); // 可以访问 x,但不能访问 y
    // Console.WriteLine(y); // 错误!y 在这里不可见
}

2.3.2 全局作用域(Global Scope):

全局变量在整个程序中可见,即使在不同的方法或类中也可以访问。你可以在类的外部或任何方法之外声明全局变量,它将在整个程序中可见。

using System;

class MyClass
{
    static int globalVariable = 10; // 全局变量,在整个程序中可见

    static void Main(string[] args)
    {
        Console.WriteLine(globalVariable); // 可以访问全局变量

        AnotherMethod();
    }

    static void AnotherMethod()
    {
        Console.WriteLine(globalVariable); // 可以访问全局变量
    }
}

需要注意的是,为了在全局作用域中声明变量,你需要将其声明为静态变量(使用static关键字)。这样做是因为全局作用域中的变量必须属于类或命名空间的范围,而不是实例的范围。

另外,为了在全局作用域中声明变量,你需要确保它是在任何方法之外声明的,通常放在类的内部。这样,变量将在整个类和程序的其他部分中可见。

2.3.3 类作用域(Class Scope):

类作用域指的是在类中声明的成员变量(字段)的范围。这些成员变量可以在类的任何方法中访问,但不能在类的外部访问。

class MyClass
{
    int classVariable = 10; // 类成员变量,在整个类中可见

    void Method1()
    {
        Console.WriteLine(classVariable); // 可以访问类成员变量
    }

    void Method2()
    {
        Console.WriteLine(classVariable); // 可以访问类成员变量
    }
}

void AnotherMethod()
{
    // Console.WriteLine(classVariable); // 错误!无法直接访问类成员变量
}

以下是作用域的错误使用示例:

2.3.4 局部变量的作用域冲突

同名的局部变量不能再同一作用域内声明两次。

public static void Example1()
{
    int x = 2; // 冲突变量“x”的定义如下
    int x = 3; // 不能在此作用域中声明名为'x'的局部变量,因为它会赋予'x'不同的含义,而'x'已经在父作用域中或当前作用域中用于表示其他内容
}

// -------------------------------------------------------------------------------------------------------------

public static void Example2()
{
    int j = 1; // 冲突变量'j'定义如下
    for (int i = 0; i < 10; i++)
    {
        int j = 2; // 不能在此作用域中声明名为'j'的局部变量,因为它会赋予'j'不同的含义,而'j'已经在父作用域中或当前作用域中用于表示其他内容
        Console.WriteLine(j + i);
    }
}

// 报错的原因是:变量 j 是在 for 循环开始之前定义的,在执行 for 循环时仍处于其作用域内,直到 Example2() 方法结束执行后,变量 j 才超出作用域。第2个 j(不合法)虽然在循环的作用域内,但作用域嵌套在 Example2() 方法的作用域内。因为编译器无法区分这两个变量,所以不允许声明第二个变量。

// -------------------------------------------------------------------------------------------------------------

// 正确示范:
// i 声明了两次的原因是作用域不同,i 在两个相互独立的循环内部声明,所以每个变量 i 对于各自的循环来说是局部变量
public static void Example3()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 9; i >= 0; i--)
    {
        Console.WriteLine(i);
    }
}

2.3.5 字段和局部变量的作用域冲突

某些情况下,可以区分名称相同(尽管其完全限定名不同)、作用域相同的两个标识符。此时编译器允许声明第二个变量。原因是 C# 在变量之前有一个基本的区分,它把在类型级别声明的变量看成字段,而把在方法中声明的变量看成局部变量。

public class VariableExample : MonoBehaviour
{
    static int j = 20; // 类级别上定义的 j,其作用域在该类被删除前;

    void Start()
    {
        LocalVariableExample();
    }

    static void LocalVariableExample()
    {
        int j = 30; // 方法中定义的 j,隐藏了同名的类级别变量,所以会输出 30;
        Console.WriteLine(j); // 30
    }
}

// 如果要输出类级别变量,可以使用:类名.字段名。
// 如果要访问实例字段,就需要使用 this 关键字。

2.4 常量

在C#中,常量(Constants)是一种特殊类型的变量,其值在编译时就确定,并且在程序执行过程中不可改变。常量在程序中用于存储不会改变的固定值,例如数学常数、配置参数或者其他固定值。

在C#中声明常量时,需要使用关键字 const。常量的命名通常使用大写字母,以便与其他变量区分开来。常量在声明时必须进行初始化,且一旦初始化后就不能再修改其值。

using System;

class Program
{
    const double Pi = 3.14159; // 声明一个常量Pi
    static void Main(string[] args)
    {
        Console.WriteLine("Pi: " + Pi); // 使用常量Pi
        double radius = 2.5;
        double circumference = 2 * Pi * radius; // 使用常量Pi进行计算
        Console.WriteLine("Circumference: " + circumference);
    }
}

常量具有如下特点:

  • 常量必须在声明时初始化。指定了其值后,就不能再改写了。

  • 常量的值必须能在编译时用于计算。因此,不能用从变量中提取的值来初始化常量。如果需要这么做,应使用只读字段 readonly。

  • 常量总是隐式静态的。但注意,不必(实际上是不允许)在常量声明中包含修饰符 static。

在程序中使用常量至少有3个好处:

  • 由于使用易于读取的名称(名称的值易于理解)代替了较难读取的数字和字符串,常量使程序变得更易于阅读。

  • 常量使程序更易于修改。

  • 常量更容易避免程序出现错误。如果在声明常量的位置以外的某个地方将另一个值赋给常量,编译器就会标记错误。

3. 预定义数据类型

3.1 值类型和引用类型

从概念上看,其区别是值类型直接存储该值,而引用类型存储对值的引用

这两种类型存储在内存的不同地方:

  • 值类型存储在堆栈(stack)上。

  • 引用类型存储在托管堆(managed heap)上。

注意区分某个类型是值类型还是引用类型,因为这会有不同的影响。

// 例如:int 是值类型,这表示下面的语句会在内存的 两个地方 存储值 20:
i = 20;
j = i;

// 但考虑下面的代码。这段代码假定已经定义了类 Vector,Vector 是一个引用类型,它有一个 int 类型的成员变量 Value:
Vector x, y;
x = new Vector(); // 只 new 了一次,所以只有一个 Vector 对象。
x.Value = 30;
y = x; // y 拿到的是引用而非对象。
Console.WriteLine(y.Value); // 30
y.Value = 50;
Console.WriteLine(x.Value); // 50

// 要理解的重要一点是:在执行这段代码后,只有一个 Vector 对象。x 和 y 都指向包含该对象的内存位置。因为 x 和 y 是引用类型的变量,声明这两个变量只保留了一个引用——而不会实例化给定类型的对象。两种情况下都不会真正创建对象。要创建对象,就必须使用 new 关键字,如上所示。因为 x 和 y 引用同一个对象,所以对 x 的修改会影响 y,反之亦然。

// 如果变量是一个引用,就可以把其值设置为 null,表示它不引用任何对象:y = null;

// 如果将引用设置为 null,显然就不可能对它调用任何非静态的成员函数或字段,这么做会在运行期间抛出一个异常。

3.2 预定义的值类型

3.2.1 整型

alt

数字分隔符:long l1 = 0x123_4567_89ab_cedf;

3.2.2 浮点类型

alt

3.2.3 decimal 类型

alt

decimal 类型不是基本类型,所以在计算时使用该类型会有性能损失。

要把数字指定为 decimal 类型而不是 double、float 或整数类型,可以在数字的后面加上字符 M(或m),如下所示:

​decimal d = 12.30M;​​

3.2.4 bool 类型

alt

bool 值和整数值不能相互隐式转换。如果变量(或函数的返回类型)声明为 bool 类型,就只能使用值 true 或 false。如果试图使用0表示 false,非0值表示true,就会出错。

3.2.5 字符类型

alt

  • char 类型用单引号括起来

  • string 类型用双引号括起来

3.2.6 数字的字面值

alt

3.3 预定义的引用类型

alt

3.3.1 object

在 C# 中,object 类型就是最终的父类型,所有内置类型和用户自定义的类型都从它派生而来。

这样,object 类型就可以用于两个目的:

  • 可以使用 object 引用来绑定任何特定子类型的对象,例如使用 object 类型把堆栈中的值对象装箱,再移动到堆中。object 引用也可以用于反射,此时必须有代码来处理类型未知的对象。

  • object 类型实现了许多一般用途的基本方法,包括 Equals()、GetHashCode()、GetType()和ToString()​​。用户定义的类需要使用一种面向对象技术——重写,来提供其中一些方法的替代实现代码。

3.3.2 string

string str1 = "Hello ";
string str2 = "World";
string str3 = str1 + str2; // Hello World

尽管这是一个值类型的赋值,但 string 是一个引用类型。string 对象被分配在堆上,而不是栈上。因此,当把一个字符串变量赋予另一个字符串时,会得到对内存中同一个字符串的两个引用。但是,string 与引用类型的常见行为有一些区别。例如,字符串是不可改变的。修改其中一个字符串,就会创建一个全新的 string 对象,而另一个字符串不发生任何改变。

string s1 = "a string";
string s2 = s1;
print($"s1 is {s1}"); // s1 is a string
print($"s2 is {s2}"); // s2 is a string
s1 = "another string"; // 创建了一个全新的 string 对象。
print($"s1 is now {s1}"); // s1 is now another string
print($"s2 is now {s2}"); // s2 is now a string

改变了 s1 的值对 s2 没有影响,原因是:当使用 a string 初始化 s1 时,就在堆上分配了一个新的 string 对象。在初始化 s2 时,引用也指向这个对象,所以 s2 的值也是 a string。但是当改变了 s1 的值时,并不会替换原来的值,而是在堆上为新值分配一个新对象。s2 变量仍指向原来的对象,所以它的值没有改变。这实际上是运算符重载的结果。

4. 程序流控制

4.1 条件语句

4.1.1 if 语句

if (condition)
    statement(s)
else
    statement(s)

4.1.2 switch 语句

switch...case 语句适用于从一组互斥的可执行分支中选择一个执行分支。

注意 case 值必须是常量表达式,不允许使用变量。

如果一条 case 子句为空,就可以从这条 case 子句跳到下一条 case 子句,这样就可以用相同的方式处理两条或多条 case 子句。

在 C# 中,switch 语句的一个有趣的地方是 case 子句的顺序是无关紧要的,甚至可以把 default 子句放在最前面。因此,任何两条 case 都不能相同。这包括值相同的不同常量,所以不能这样编写:

static void Main()
{
    string country = string.Empty;
    string language = string.Empty;
    const string england = "uk";
    const string britain = "uk";
    switch (country)
    {
        case england: // 重复的case标签值“uk”
        case britain: // 重复的case标签值“uk”
            language = "English";
            break;
    }
}

// 由上还可以看出,在 C# 中的 switch语句中,可以把字符串用作测试的变量。

4.2 循环

4.2.1 for 循环

for 循环是所谓的预测试循环,因为循环条件是在执行循环语句前计算的,如果循环条件为假,循环语句就根本不会执行。

for (initializer /* 1 */; condition/* 2 */; iterator/* 4 */)
{
    // 循环代码 /* 3 */
}
  • initializer 是指在执行第一次循环前要计算的表达式(通常把一个局部变量初始化为循环计数器)。

    • 初始化,只执行一次。
  • condition 是在每次循环的新迭代之前要测试的表达式(它必须等于 true,才能执行下一次迭代)。

    • 判断条件是否成立。
  • iterator 是每次迭代完要计算的表达式(通常是递增循环计数器)。

    当 condition 等于 false 时,迭代停止。

for 循环非常适合用于一条语句或语句块重复执行预定的次数。

4.2.2 while 循环

与 for 循环一样,while 循环也是一个预测试循环。

while (condition)
    statement(s);

与 for 循环不同的是,while 循环最常用于以下情况:

在循环开始前,不知道重复执行一条语句或语句块的次数。通常,在某次迭代中,while 循环体中的语句把布尔标志设置为 false,结束循环。

4.2.3 do...while 循环

do...while 循环是 while 循环的后测试版本。这意味着该循环的测试条件要在执行完循环体之后评估。因此do...while 循环适用于循环体至少执行一次的情况。

do
{
    statement(s);
} 
while (condition)

4.2.4 foreach 循环

foreach 循环可以迭代集合中的每一项。

foreach (var item in array)
{
    statement(s);
}

foreach 循环每次迭代数组中的一个元素。它会把每个元素的值放在 var 类型的变量 item 中,然后执行一次循环迭代。

注意:foreach 循环不能改变集合中各项(item)的值。

如果需要迭代集合中的各项,并改变它们的值,应使用 for 循环。

4.3 跳转语句

4.3.1 goto 语句

在C#中,goto语句是一种流程控制语句,它允许你无条件地跳转到程序中的标签位置。尽管goto语句可以改变正常的程序流程,但它在现代编程实践中很少被使用,因为它可能导致代码难以理解和维护。

goto语句的基本语法如下:

goto label;

其中,label是一个标识符,表示你想要跳转到的代码位置。

以下是一个使用goto语句的示例:

start:
    Console.WriteLine("Enter a number:");

    string input = Console.ReadLine();
    int number = int.Parse(input);

    if (number < 0)
        goto start;

    Console.WriteLine("Number is positive.");

在上面的示例中,我们使用了一个goto语句来实现一个简单的循环。如果用户输入的数值小于0,程序将跳转到start标签处重新开始循环,直到输入一个非负数为止。

尽管goto语句可以实现一些特定的控制流程需求,但在大多数情况下,更好的做法是使用循环结构(如forwhiledo-while)或条件语句(如ifswitch)来实现控制流程。这些结构更易读、易理解,并且更容易进行维护。

在实际编程中,尽量避免使用goto语句,除非你有充分的理由并且清楚其影响。使用结构化的控制流程可以使代码更加可靠和易于理解。

4.3.2 break 语句

在C#中,break语句用于在循环或switch语句中立即终止当前的迭代或条件分支,并跳出循环或switch语句的执行。

在循环中使用break语句:

在循环中使用break语句时,它会立即终止当前循环的执行,并跳出循环。这在需要提前结束循环的情况下非常有用。

以下是一个使用break语句的示例:

for (int i = 1; i <= 10; i++)
{
    if (i == 6)
        break; // 当i等于6时跳出循环

    Console.WriteLine(i);
}

在上面的示例中,当变量i的值等于6时,break语句被执行,导致循环立即终止。因此,只会输出1到5的数字。

switch语句中使用break语句:

switch语句中使用break语句非常重要,因为它用于结束每个case分支。在switch语句中,如果没有使用break语句,程序将会继续执行后续的case分支,直到遇到break语句或switch语句结束。

以下是一个使用break语句的switch语句示例:

int day = 4;
string dayName = "";

switch (day)
{
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    case 4:
        dayName = "Thursday";
        break;
    case 5:
        dayName = "Friday";
        break;
    default:
        dayName = "Unknown";
        break;
}
Console.WriteLine(dayName);

在上面的示例中,根据变量day的值,switch语句选择相应的case分支并将dayName赋值为相应的字符串。每个case分支后面都有一个break语句,它用于结束当前分支的执行。如果没有break语句,程序将继续执行后续的case分支,直到遇到break语句或switch语句结束。

总结:break语句用于在循环或switch语句中立即终止当前的迭代或条件分支,并跳出循环或switch语句的执行。在循环中使用break语句可以提前结束循环,而在switch语句中使用break语句可以确保每个case分支的执行结束。

4.3.3 continue 语句

在C#中,continue语句用于在循环中跳过当前迭代的剩余代码,并继续下一次迭代。它会立即中止当前迭代中的代码执行,然后跳到循环的下一次迭代。

使用continue语句时,循环的控制流程将直接转到循环的迭代条件处,而不执行循环体中剩余的代码。这可以帮助我们跳过某些特定条件下的操作,而不中断整个循环。

以下是使用continue语句的示例:

for (int i = 1; i <= 10; i++)
{
    if (i % 2 == 0)
        continue; // 如果i是偶数,则跳过当前迭代

    Console.WriteLine(i);
}

在上面的示例中,当变量i的值是偶数时,continue语句被执行,导致当前迭代的剩余代码被跳过。因此,只会输出奇数。

continue语句对于在循环中处理特定条件的代码非常有用。通过使用continue语句,我们可以避免在满足特定条件时执行不必要的代码,从而提高代码效率和可读性。

需要注意的是,在switch语句中并没有使用continue语句。在switch语句中使用continue语句是非法的。continue语句只能在循环中使用。

总结:continue语句用于在循环中跳过当前迭代的剩余代码,并继续下一次迭代。它会立即中止当前迭代中的代码执行,然后跳到循环的下一次迭代。这对于跳过特定条件下的代码执行很有用。

4.3.4 return 语句

在C#中,return语句用于从方法中返回值,并立即终止方法的执行。它可以将控制流程返回到调用方法的位置,并且可以选择性地返回一个值。

返回值的语法:

return [expression];

在上述语法中,expression是可选的,它表示要返回的值。如果方法没有返回值(void方法),则return语句可以在不带表达式的情况下使用,只是用于终止方法的执行。

以下是一些return语句的示例:

方法没有返回值的情况(void方法):

void DisplayMessage()
{
    Console.WriteLine("Hello, World!");

    // 无返回值的方法中可以直接使用 return; 终止方法的执行
    return;
}

方法返回一个值的情况:

int AddNumbers(int a, int b)
{
    int sum = a + b;

    // 使用 return 语句返回一个值
    return sum;
}

方法返回一个引用类型的对象:

Person CreatePerson(string name, int age)
{
    Person person = new Person(name, age);

    // 使用 return 语句返回一个对象的引用
    return person;
}

return语句可以在方法的任何位置使用。一旦执行到return语句,方法的执行将立即停止,并将控制流程返回给调用方。

需要注意的是,当方法有返回值时,return语句后面的表达式的类型必须与方法的返回类型相匹配。如果方法声明了一个非void的返回类型,但没有return语句或者return语句没有表达式,编译器将会报错。

总结:return语句用于从方法中返回值,并立即终止方法的执行。它将控制流程返回到调用方法的位置,并可以选择性地返回一个值。在void方法中,可以使用return;语句终止方法的执行。在有返回值的方法中,return语句后面的表达式的类型必须与方法的返回类型相匹配。

5. 名称空间(命名空间)

在C#中,命名空间(Namespace)用于组织和管理代码,以便更好地组织和分类类型(类、结构体、接口等)。命名空间提供了一种将相关的类型和成员分组的方式,避免命名冲突,并提供了代码的可读性和可维护性。

声明命名空间:

命名空间的声明使用namespace关键字,后面跟着命名空间的名称。通常,命名空间的名称与文件夹结构和项目的层次结构相对应,以便更好地组织代码。

以下是一个命名空间的示例:

namespace MyNamespace
{
    // 类、结构体、接口和其他成员的声明
    class MyClass
    {
        // 类成员的声明
    }
}

在上面的示例中,我们声明了一个名为MyNamespace的命名空间,并在其中定义了一个名为MyClass的类。

使用命名空间:

在使用命名空间中的类型和成员之前,我们需要在文件的开头使用using指令来导入所需的命名空间。这样可以避免每次使用类型和成员时都写完整的命名空间路径。

以下是使用命名空间的示例:

using System;
using MyNamespace;

class Program
{
    static void Main()
    {
        // 使用命名空间中的类型
        MyClass myObject = new MyClass();

        // 使用其他命名空间的类型
        DateTime now = DateTime.Now;
    }
}

在上面的示例中,我们使用using指令导入了System命名空间和MyNamespace命名空间。这样,在Main方法中我们可以直接使用MyClass类型而无需完整地指定命名空间路径。

需要注意的是,如果出现了多个命名空间中有同名的类型,或者需要使用同一个命名空间中的多个类型,可以使用别名来区分或简化使用。

总结:命名空间是C#中组织和管理代码的机制。它提供了一种将相关的类型和成员分组的方式,避免命名冲突,并提供了代码的可读性和可维护性。使用namespace关键字来声明命名空间,在使用命名空间中的类型和成员之前,使用using指令导入所需的命名空间。

6. 使用注释

6.1 普通注释

// 第一种注释 -> 单行注释
/* 第二种注释 -> 多行注释 */

6.2 XML 文档

XML文档注释参考文档

alt

7. C# 编程准则

7.1 命名约定

C# 编码约定 - Microsoft Learn

C# 编码样式 - Github

7.2 属性和方法的使用

在C#编程中,属性(Properties)和方法(Methods)都用于封装数据和行为,但它们在使用方式和目的上有一些区别。

**使用属性:**属性用于封装对象的状态或数据,并提供对该数据的访问和修改。属性通常用于访问对象的成员变量(字段),并且可以在内部进行逻辑处理。

以下是一些适合使用属性的情况:

  • 对字段进行简单的访问和设置: 当仅需要简单地访问和设置对象的字段时,可以使用属性来提供对字段的访问器(getter)和设置器(setter)。

  • 数据验证和保护: 属性提供了对数据进行验证和保护的机制。通过属性的设置器,可以在对属性值进行赋值之前执行验证逻辑,确保数据的有效性。

  • 计算属性: 属性还可以用于根据其他数据进行计算并返回一个结果。这样,可以隐藏计算的细节,并通过属性的方式提供访问结果。

**使用方法:**方法用于封装对象的行为和操作,它们定义了可以执行的操作和所需的参数。方法可以包含复杂的逻辑和算法,并且可以返回一个值(如果需要)。

以下是一些适合使用方法的情况:

  • 执行复杂的计算或算法: 如果需要执行复杂的计算或算法,可能涉及多个步骤和逻辑,这时使用方法更合适。

  • 修改对象的状态: 如果需要修改对象的状态或执行某些操作,例如对列表进行排序或执行其他变更操作,应该使用方法而不是属性。

  • 执行需要参数的操作: 如果需要向方法传递参数以影响其行为,例如在方法中执行某些特定操作或计算,那么使用方法是必要的。

类中出现混乱的一个方面是某个特定数量是用属性还是方法来表示。这没有硬性规定,但一般情况下,如果该对象的外观像变量,就应使用属性来表示它,即:

  • 客户端代码应能读取它的值。最好不要使用只写属性,例如,应使用SetXXX()​​方法,而不是XXX​​只写属性。

  • 读取该值不应花太长的时间。实际上,如果是属性,通常表明读取过程花的时间相对较短。

  • 顺序读取属性应有相同的结果。如果属性的值可能会出现预料不到的改变,就应把它编写成一个方法。在监控汽车运动的类中,把 speed 设置为属性就不合适,而应使用GetSpeed()​​方法;另一方面,应把 Weigh 和 EngineSize 设置为属性,因为对于给定的对象,它们是不变的。

7.3 字段的使用

字段的用法非常简单。字段应总是私有(private)的,但在某些情况下也可以把常量或只读字段设置为公有。原因是如果把字段设置为公有,就不利于在以后扩展或修改类。

C#中的字段一般设置为私有的主要原因是封装和数据隐藏。

封装和数据隐藏: 将字段设置为私有意味着只能在类或结构体的内部访问该字段,而无法从外部直接访问。这样可以实现封装的概念,隐藏了对象的内部实现细节和数据结构。

通过将字段设置为私有,可以防止外部代码直接访问和修改字段的值,从而确保数据的完整性和安全性。外部代码只能通过公共(public)的方法或属性来间接访问和修改字段的值,这样可以提供更好的控制和验证数据的访问。

数据一致性和可维护性: 通过将字段设置为私有,可以确保在对象的内部进行数据一致性的处理和维护。只有通过类或结构体内部的方法或属性来修改字段的值,可以在更高的层次上实施验证逻辑、数据校验和其他处理,以保证数据的正确性和一致性。

此外,私有字段还允许对其进行更灵活的修改,而无需影响外部代码。如果将字段设置为公共的,外部代码可以直接访问和修改字段的值,这可能导致字段的不受控制的修改,增加了代码的脆弱性和不稳定性。

封装的示例:

下面是一个示例,展示了为什么字段一般设置为私有的:

class Person
{
  private string m_Name; // 私有字段
  public string Name => m_Name;
  public void SetName(string newName)
  {
      // 对字段进行验证和处理
      if (!string.IsNullOrEmpty(newName))
      {
          name = newName;
      }
  }
}

在上面的示例中,字段name被设置为私有,外部代码无法直接访问它。相反,通过公共的Name只读属性和SetName方法来间接访问和修改字段的值,从而实现了对字段的控制和验证。

#知识点##技术分享##学习笔记##学习##CSharp#
C# 知识库 文章被收录于专栏

C#知识库用于学习和复习C#知识~

全部评论
欢迎各位读者指出错误!
点赞
送花
回复
分享
发布于 2023-06-02 11:16 广西
访问修饰符有什么区别?
点赞
送花
回复
分享
发布于 2023-06-02 16:06 广东
蔚来
校招火热招聘中
官网直投
大佬,前端转c#有什么方向,前端真的找不到工作😇
点赞
送花
回复
分享
发布于 2023-10-31 07:50 河南

相关推荐

#我的实习求职记录#总结就是:开始是突然的,过程是曲折的,结果是圆满的。美国没找到实习,一开始没想着回国找实习,但碍于简历实在单薄加上技术栈羸弱不堪,对现在的就业环境也感到焦虑和紧张,就想着积累点面试经验。3月中旬投递(投了没几个,米哈游,鹰角,腾讯,网易雷火),后面3月底又陆陆续续补了莉莉丝、完美、西山居、灵犀等,但没超过十个。笔试基本都有接到,但有后续回应的只有米哈游(直接挂)、鹰角、腾讯和雷火,其他的只有灵犀在5月11号联系我说约面试(被我拒了,太晚了),其他的至今还没动静。由于字数有限制,面经就不细发了,有需要的小伙伴可以私信,我看到会回(但可能会有1-3天延迟),主要还是流程的记录,以下三家公司按Offer时间顺序排列,提到的项目相关如无其他说明都是游戏(主要是game&nbsp;jam...)鹰角网络:笔试:4月8日,三道算法题,2道90,还有1道0(没写完dwb)一面:4月11日,一道简单算法题(手撕+延申口述),一些八股(主要是C++),也有少数C#和图形学,简历项目相关。二面:4月22日,总管面,先是问了问游戏经历相关,从经历出发问一些问题(但跟策划有些挂钩),然后是一道算法题和简历项目(但也有涉及行为面的意思)HRBP面:4月28日意向:4月30日Offer:5月6日网易雷火:笔试:3月30日,忘了几道题了(4还5),最后一题没写出来。一面:4月9日,简历项目相关,笔试(有选择填空算法),八股(主要C++、图形学)二面:4月17日,算法题一道(手撕),问了我简历项目(非游戏,是问的毕设一个图形学的项目)三面:4月25日,交叉面,问项目,问了几个游戏里的功能实现,聊了点引擎技术,问了(非常少)C++八股,笔试题x3(口述)。HR面:4月30日意向:5月7日Offer:5月8日(催过)腾讯:前两个部门都挂了,天美:电话面试(一面):3月26日,二面4月9日,都是八股没聊项目。魔方:一面4月12日,二面4月20日,一面是八股,二面很难的八股。光子:一面:4月26日,普通八股。二面:4月29日,项目相关,毕设(图形学)相关三面:5月7日,项目相关。HR面:5月8日,当日转录用评估Offer:5月11日(催过)最后选择了鹅,有面试的都拿到offer了,除了没能在美国找到实习外第一年算是圆满结束。
投递完美世界等公司10个岗位 我的实习求职记录
点赞 评论 收藏
转发
9 23 评论
分享
牛客网
牛客企业服务