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 类型和 floatdouble 类型之间不存在隐式转换。使用显式数值转换decimal 转换为 floatdouble 时,源值分别舍入为最接近的 floatdouble 值。
  • 类型 int 的常量表达式的值(例如,由整数文本所表示的值)如果在目标类型的范围内,则可隐式转换为 sbytebyteshortushortuintulongnintnuint
  • 强制转换表达式:形式为 (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 修饰。若可以成功转换,则该方法返回 trueout 修饰的参数被修改为转换后的结果;若转换失败,该方法返回 falseout 修饰的参数不变。

Convert.ToInt32()

数组 array

长度:Length属性。

声明方式

  1. // 只确定数组的类型和长度,没有进行赋值。
    int[] nums = new int[5];
    
  2. // 确定了数组的类型,进行了赋值,长度为值的个数。
    int[] nums = {1, 2, 3, 4, 5};
    
  3. // 以下两种不推荐,使用第 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

  1. 不指定异常类型

    try
    {
    
    }
    catch
    {
    
    }
    
  2. 指定异常类型

    try
    {
    
    }
    catch (Exception)
    {
    
    }
    
  3. 引入异常的局部变量

     try
    {
    
    }
    catch (Exception e)
    {
    
    }
    
  4. 捕获多个异常类型,要求更大的异常应该写在更小的异常下面

    try
    {
    
    }
    catch (IOException e)
    {
    
    }
    catch (Exception e)
    {
    
    }
    
  5. finally 写在 catch 块下面,也可以省略 catch 块。

    try
    {
    
    }
    finally
    {
    
    }
    

枚举 enum

public Enum 枚举名
{
    枚举值1,
    枚举值2
}

枚举与 intString 的转换

枚举与 int 类型的互相转换使用强制类型转换。

枚举转 stringe.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 修饰类,表示该类不能被继承。

newoverride

在C#中,newoverride都是用于方法重写(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#中,显式实现接口是一种实现接口的方式,它允许一个类实现多个具有相同名称但不同参数的接口。显式实现接口的方式是将方法实现标记为接口成员。以下是如何显式实现接口的步骤:

  1. 首先,在类中声明要实现的接口,并定义所有需要实现的接口成员。

    interface IExampleInterface
    {
        void ExampleMethod();
    }
    
    class ExampleClass : IExampleInterface
    {
        void IExampleInterface.ExampleMethod()
        {
            // 实现IExampleInterface接口的方法
        }
    }
    
  2. 然后,在实现接口成员时,在方法名前加上接口名称和".",并将其标记为接口成员。这告诉编译器该方法是要显式实现接口的成员而不是类的成员。

    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; }
    }
}
posted @ 2025-08-18 20:46  Varc  阅读(22)  评论(0)    收藏  举报