C# 基础
.NET 框架版本默认 C# 语言版本
| 框架 | 版本 | C# 语言版本的默认值 |
|---|---|---|
| .NET | 10.x | C# 14 |
| .NET | 9.x | C# 13 |
| .NET | 8.x | C# 12 |
| .NET | 7.x | C# 11 |
| .NET | 6.x | C# 10 |
| .NET | 5.x | C# 9.0 |
| .NET Core | 3.x | C# 8.0 |
| .NET Core | 2.x | C# 7.3 |
| .NET Standard | 2.1 | C# 8.0 |
| .NET Standard | 2.0 | C# 7.3 |
| .NET Standard | 1.x | C# 7.3 |
| .NET Framework | 全部 | C# 7.3 |
命名空间
类似 Java 的 package。
Java 导包:import package
C# 引用命名空间:using namespace
变量与常量
变量 variable
可以使用 var 关键字声明变量。语法糖
常量 const
const int number = 10;
number = 20; // 错误
数据类型
| 字节数 | 整型 | 浮点型 | 其他类型 |
|---|---|---|---|
| 1 | byte | bool | |
| 2 | short | char | |
| 4 | int | float | |
| 8 | long | double | |
| 16 | decimal |
字符串 string
可以使用 [] 通过索引取字符,不需要像 Java 一样调用 charAt(index) 方法。
字符串拼接
可以使用 + 操作符。可以直接连接数字与字符串。
Console.WriteLine("31" + 1); // 311
Console.WriteLine(1 + "31"); // 不同于 Java,131
StringBuilder
使用 + 操作符进行字符串拼接,底层使用的就是 StringBuilder。与 Java 相同。
逐字字符串 @
Console.WriteLine(@" te\s\\t
next line");
输出:
te\s\\t
next line
字符串内插 $
int version = 11;
string updateText = "Update to Windows";
Console.WriteLine($"{updateText} {version}");
同时使用 $@
string projectName = "First-Project";
Console.WriteLine($@"C:\Output\{projectName}\Data");
格式化输出
string first = "Hello";
string second = "World";
Console.WriteLine("{1} {0}!", first, second); // World Hello!
Console.WriteLine("{0} {0} {0}!", first, second); // Hello Hello Hello!
推荐使用字符串内插的方式。
货币格式
:C:货币格式,显示字符与 Windows 显示语言设置相关。无论使用 int 还是 decimal,在大括号内的标记中添加 :C 都会将数字格式化为货币。(区域性影响书写系统、使用的日历、字符串的排序顺序以及日期和数字的格式设置(如货币格式设置)。)
之后添加一个数字来控制小数点后显示位数。
decimal price = 123.45m;
int discount = 50;
Console.WriteLine($"Price: {price:C} (Save {discount:C})"); // Price: ¥123.45 (Save ¥50.00)
数值格式
N:使数字更具可读性。N 数值格式说明符默认仅显示小数点后两位数字(四舍五入)。之后添加一个数字来控制小数点后显示位数。
decimal measurement = 123456.78912m;
Console.WriteLine($"Measurement: {measurement:N} units"); // Measurement: 123,456.79 units
N4:显示小数点后四位数字。
decimal measurement = 123456.78912m;
Console.WriteLine($"Measurement: {measurement:N4} units"); // Measurement: 123,456.7891 units
百分比的格式
P 显示百分比的格式,之后添加一个数字来控制小数点后显示位数。
decimal tax = .36785m;
Console.WriteLine($"Tax rate: {tax:P2}"); // Tax rate: 36.79 %
填充和对齐
见常用方法中的 PadLeft()、PadRight()。
常用方法
添加空白进行格式设置的方法(PadLeft()、PadRight())
比较两个字符串或辅助比较的方法(Trim()、TrimStart()、TrimEnd()、GetHashcode()、Length 属性)
帮助确定字符串内部内容,甚至只检索部分字符串的方法(IndexOf()、Contains()、StartsWith()、EndsWith()、Substring())注意:Substring 的两个参数分别为起始位置和长度,与 Java 起始位置和终止位置不同。
通过替换、插入或删除部件来更改字符串内容的方法(Replace()、Insert()、Remove())
将字符串转换为字符串或字符数组的方法(Split()、ToCharArray())
数字
数学运算
+`、`-`、`*`、`/`、`%
类型转换(隐式、显式/收缩 cast)
- 任何整型数值类型都可以隐式转换为任何浮点数值类型。
decimal类型和float或double类型之间不存在隐式转换。使用显式数值转换将decimal转换为float或double时,源值分别舍入为最接近的float或double值。- 类型
int的常量表达式的值(例如,由整数文本所表示的值)如果在目标类型的范围内,则可隐式转换为sbyte、byte、short、ushort、uint、ulong、nint或nuint: - 强制转换表达式:形式为
(T)E的强制转换表达式将表达式E的结果显式转换为类型T。
调用方法执行类型转换
以转换为 int 为例。使用此种方法转换与强制转换不同,例如将 decimal 转换为 int,使用收缩转换方式会截断,而使用下面的方法会进行舍入。
int value = (int)1.5m; // casting truncates
Console.WriteLine(value); // 1
int value2 = Convert.ToInt32(1.5m); // converting rounds up
Console.WriteLine(value2); // 2
int.Parse()、int.TryParse()
推荐使用 int.TryParse(),传入两个参数,第二个参数使用 out 修饰。若可以成功转换,则该方法返回 true,out 修饰的参数被修改为转换后的结果;若转换失败,该方法返回 false,out 修饰的参数不变。
Convert.ToInt32()
数组 array
长度:Length属性。
声明方式
-
// 只确定数组的类型和长度,没有进行赋值。 int[] nums = new int[5]; -
// 确定了数组的类型,进行了赋值,长度为值的个数。 int[] nums = {1, 2, 3, 4, 5}; -
// 以下两种不推荐,使用第 2 中方法代替。 int[] nums = new int[5] {1, 2, 3, 4, 5}; int[] nums = new int[] {1, 2, 3, 4, 5};
Array 类
排序:Array.Sort();
反转:Array.Reverse();
清除数组:Array.Clear(array, fromIndex, clearLength);
调整数组大小:Array.Resize(ref array, size);
流程控制
foreach
string[] names = { "Rowena", "Robin", "Bao" };
foreach (string name in names)
{
Console.WriteLine(name);
}
注意,使用 foreach 进行遍历时,不能修改迭代变量(例如下面代码中的 name)。它指向原始数组中对应的元素。因此,当尝试修改迭代变量时,实际上是在尝试修改原始数组中的值。
string[] names = { "Alex", "Eddie", "David", "Michael" };
foreach (var name in names)
{
// Can't do this:
if (name == "David") name = "Sammy";
}
可以使用 for 语句破除 foreach 语句的局限。
switch
与 Java 区别:C# 不允许从一个 case 部分继续执行到下一个 case 部分。如果 case 语句中有已经执行,则必须包含 break 或其他跳转语句。
异常 exception
-
不指定异常类型
try { } catch { } -
指定异常类型
try { } catch (Exception) { } -
引入异常的局部变量
try { } catch (Exception e) { } -
捕获多个异常类型,要求更大的异常应该写在更小的异常下面
try { } catch (IOException e) { } catch (Exception e) { } -
finally写在catch块下面,也可以省略catch块。try { } finally { }
枚举 enum
public Enum 枚举名
{
枚举值1,
枚举值2
}
枚举与 int、String 的转换
枚举与 int 类型的互相转换使用强制类型转换。
枚举转 string:e.ToString()
string 转枚举:Enum.Parse(string) 或 Enum.TryParse(string, out enum)
结构 struct
一次性声明多个不同类型的变量。
public struct 结构名
{
public string 字段1;
public int 字段2;
public bool 字段3;
}
访问权限修饰符
private:私有的权限,可以用来修饰字段、属性和方法。只能在当前类中访问;字段、属性和方法的默认访问权限就是private。
protected:可以用来修饰字段、属性和方法。可以在当前类中访问,也可以在子类(包含子类的子类)及跨项目继承的子类中访问。
internal:可以用来修饰类、字段、属性和方法。在整个项目中生效。类的默认的访问权限是internal;
protected internal:可以用来修饰字段、属性和方法,在当前项目中生效,在跨项目的子类中生效。
public:可以用来修饰类、字段、属性和方法,在整个解决方案生效。
方法 method
out
使方法返回多个值,例如 TryParse 方法。
要求在方法调用结束之前必须对 out 修饰的参数赋值。
// 方法声明(例子为除法计算求商和余数)
void Divide(int dividend, int divisor, out int quotient, out int remainder)
{
quotient = dividend / divisor;
remainder = dividend % divisor;
}
// 方法调用
int quotient;
int remainder;
Divide(100, 9, out quotient, out remainder);
Console.WriteLine($"quotient={quotient}, remainder={remainder}");
// 也可以在 out 处声明
Divide(100, 9, out int quotient, out int remainder);
Console.WriteLine($"quotient={quotient}, remainder={remainder}");
ref
相当于 C++ 中使用 & 传递参数。使得在方法内可以改变实参。
要求使用 ref 传递参数必须在调用方法之前为参数赋值。(局部变量使用之前必须先赋值)
// 方法声明
void Swap(ref int a, ref int b)
{
int t = a;
a = b;
b = t;
}
// 方法调用
int x = 1; int y = 2;
Swap(ref x,ref y);
Console.WriteLine($"x={x}, y={y}"); // x=2, y=1
使用命名参数
// 方法声明
void Fun(int i, string s, bool b) { }
// 方法调用
Fun(1, b: true, s: "123");
// Fun(s: "123", i: 1, true); // 错误,所有命名参数都要在未命名参数后面
声明可选参数
可以节省代码量,免去重载多个方法。
要求可选参数必须出现在所有必须参数之后。
// 方法声明
void Fun(int i, string s = "123", bool b = true) { }
// 方法调用
Fun(1);
Fun(1, "321");
Fun(1, "321", false);
Fun(s: "321", i: 1);
Fun(b: false, i: 1);
声明可变参数 params
传递多个相同类型的参数,但参数个数不确定时,不需要提前声明数组,可以直接传递多个参数。
要求参数数组必须是参数列表中的最后一个参数。
// 方法声明
int Sum(params int[] nums)
{
int sum = 0;
foreach (int num in nums)
{
sum += num;
}
return sum;
}
// 方法调用
int total1 = Sum(1, 2, 3); // total1 = 6
int total2 = Sum(4, 5, 6, 7, 8); // total2 = 30
int total3 = Sum(); // total3 = 0
// 也可以直接传递一个数组
int[] nums = {9, 10};
int total4 = Sum(nums); // total4 = 19
类 class
字段 field
即成员变量。
属性 property
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
// 简化
private string _name;
public string Name { get => _name; set => _name = value; }
// 再简化(自动实现的属性)
public string Name { get; set; }
// 声明并初始化
public string Name { get; set; } = "John Doe";
// 限制修改
public string Name { get; }
静态 static
不同于 Java,C# 可以使用 static 修饰 class,以声明静态类。(Java 中只能实现静态内部类)
要求不能在静态类中声明实例成员,也不能对静态类进行实例化。
通常用于实现工具类。
继承 extend
区别于 Java 的 extends,C# 使用 :
class Fruit
{
}
class Apple : Fruit
{
}
C# 同样不支持多继承。
继承与构造函数
子类可以通过调用基类的构造函数来初始化从基类继承的成员,但是子类不能直接访问或调用基类的构造函数。
子类的构造函数默认会先调用基类的无参构造函数。如果基类中没有无参构造函数,可以在子类显示调用基类有参的构造函数,使用 :。
class Fruit
{
public Fruit(string name) { }
}
class Apple : Fruit
{
public Apple(string name) : base(name) { }
}
与 Java 相同的地方在于子类会调用父类的构造函数。
不同的地方在于语法不同。 Java 的调用会写在方法内部的第一行,而 C# 的调用写在
:后。
密封 sealed
使用 sealed 修饰类,表示该类不能被继承。
new 与 override
在C#中,new和override都是用于方法重写(method overriding)的关键字,但它们有不同的作用。
new关键字表示定义了一个新的方法,并且该方法与基类中定义的具有相同名称的方法没有任何关系。使用new关键字时,子类中的该方法将隐藏(而不是覆盖)基类中的相应方法。这意味着,如果通过基类引用调用该方法,则将调用基类方法;如果通过子类引用调用该方法,则将调用子类方法。例如:
class A
{
public void Method1()
{
Console.WriteLine("A.Method1");
}
}
class B : A
{
public new void Method1()
{
Console.WriteLine("B.Method1");
}
}
A obj1 = new A();
B obj2 = new B();
A obj3 = new B();
obj1.Method1(); // 输出 "A.Method1"
obj2.Method1(); // 输出 "B.Method1"
obj3.Method1(); // 输出 "A.Method1",因为通过基类引用调用该方法
override关键字表示子类中的该方法将覆盖基类中的相应方法。这意味着,无论是通过基类引用还是子类引用调用该方法,都将调用子类方法。必须确保子类方法签名与基类方法签名完全匹配,否则编译器将发生错误。同时被重写的方法需要声明为virtual。例如:
class A
{
public virtual void Method1()
{
Console.WriteLine("A.Method1");
}
}
class B : A
{
public override void Method1()
{
Console.WriteLine("B.Method1");
}
}
A obj1 = new A();
B obj2 = new B();
A obj3 = new B();
obj1.Method1(); // 输出 "A.Method1"
obj2.Method1(); // 输出 "B.Method1"
obj3.Method1(); // 输出 "B.Method1",因为通过子类引用调用该方法
总之,new关键字表示定义了一个新的方法,override关键字表示覆盖了基类中的相应方法。在使用时需要根据实际需求选择合适的关键字。
里氏转换
类似 Java 的多态。
as
父类向子类转型。如果转换失败,赋值为 null。(在 Java 中只能使用强制类型转换)
Apple apple = fruit as Apple;
is
类似 Java 的 instanceof,检查某个对象是否为某类型或父类。返回结果为 bool 类型。
抽象 abstract
抽象方法只能在抽象类中声明,但是抽象类中可以声明实例方法。
抽象方法不能使用 private 修饰。
接口 interface
C# 的旧版本同 Java 的旧版本一样,不支持默认接口实现。
实现接口与继承类语法相同,都是在 : 后加实现的接口(注意,必须写在继承的基类或抽象类之后)。
接口也可以继承,但是接口只能继承于接口,不能继承类。
显式实现接口(避免)
在C#中,显式实现接口是一种实现接口的方式,它允许一个类实现多个具有相同名称但不同参数的接口。显式实现接口的方式是将方法实现标记为接口成员。以下是如何显式实现接口的步骤:
-
首先,在类中声明要实现的接口,并定义所有需要实现的接口成员。
interface IExampleInterface { void ExampleMethod(); } class ExampleClass : IExampleInterface { void IExampleInterface.ExampleMethod() { // 实现IExampleInterface接口的方法 } } -
然后,在实现接口成员时,在方法名前加上接口名称和".",并将其标记为接口成员。这告诉编译器该方法是要显式实现接口的成员而不是类的成员。
void IExampleInterface.ExampleMethod() { // 实现IExampleInterface接口的方法 }
需要注意的是,显式实现接口的方法不能使用public、private、protected或 internal 修饰符,因为这些修饰符会自动将其标记为类成员。显式实现接口的方法只能通过接口来调用,无法通过类实例直接调用。
索引器
可以理解为实现了一种 [] 方法,使该类可以像数组或字典一样通过 [] 来获取一些值。
class MyClass
{
private int[] _data = new int[10];
public int this[int i]
{
get { return _data[i]; }
set { _data[i] = value; }
}
}

浙公网安备 33010602011771号