1. 认识C#世界
1.1. 什么是.NET?
想象一下,.NET就像是一个巨大的乐高积木城堡,由Microsoft精心设计并慷慨开源。这个平台让你能够使用多种语言(C#、F#或Visual Basic)、各种编辑器和丰富的库来构建各类应用程序。无论是Web、移动、桌面、游戏还是物联网应用,.NET都能帮你搞定。
.NET不仅仅是一个框架,它是一个完整的生态系统,提供了跨平台支持、一致的API、丰富的库(通过NuGet包管理器,这里有超过10万的软件包)以及多种应用模型。
小贴士:当你在编写C#代码时,其实你是在.NET这个大舞台上表演,而C#只是你选择的表演语言。
1.2. 什么是C#?
C#(读作"C-Sharp")是一种简单、现代、面向对象且类型安全的编程语言。它是.NET平台上最受欢迎的语言,特别适合游戏开发(Unity和Godot引擎都使用C#)。
想象C#是一把瑞士军刀:它锋利(性能好)、多功能(支持多种编程范式)、而且设计精良(语法简洁明了)。它不像C++那样复杂,也不像Python那样随意,它找到了一个完美的平衡点。
1.3. .NET交互模式
在.NET世界中有两种主要的交互模式:
- C/S模式(客户端/服务器):需要安装专门的客户端软件,如桌面应用程序。就像你必须下载微信才能和朋友聊天一样。
- B/S模式(浏览器/服务器):只需要一个浏览器,如Web应用程序。就像直接通过网页版微信聊天,无需安装任何软件。
1.4. 开发工具
Visual Studio是C#开发的首选IDE(集成开发环境),它就像程序员的驾驶舱,提供了代码编辑、调试、测试、部署等全方位功能。Visual Studio 2022是最新版本,支持64位架构,性能更佳。
有趣事实:Visual Studio有时候占用的内存比你的整个应用程序还要多,但它提供的生产力提升绝对值得这些资源消耗!
2. C#语法基础
2.1. C#程序的一般结构
using System; // 引入命名空间,就像Python的import
namespace YourNamespace // 命名空间,防止名称冲突
{
class YourClass // 类定义,C#中一切皆对象
{
// 程序入口点
static void Main(string[] args)
{
Console.WriteLine("Hello world!"); // 输出到控制台
Console.ReadKey(); // 等待按键,防止控制台闪退
}
}
}
C#代码规则:
- 所有标点符号必须是英文半角
- 每行代码以分号(;)结束
- 代码区分大小写
- 使用大括号{}定义代码块
2.2. 注释:与未来的自己对话
代码是写给人看的,只是恰好能让机器执行。好的注释能让你在六个月后不至于对着自己的代码问:"这是我写的?我当时在想什么?"
C#支持三种注释:
-
单行注释:用
//开头int a = 10; // 定义一个整数变量a -
多行注释:用
/*开头,*/结尾/* 这是一个多行注释 可以跨越多行 */ -
XML文档注释:用
///开头,为代码生成文档/// <summary> /// 计算两个数的和 /// </summary> /// <param name="a">第一个数</param> /// <param name="b">第二个数</param> /// <returns>两数之和</returns> public int Add(int a, int b) { return a + b; }
2.3. 基本数据类型
C#中的变量分为三类:值类型、引用类型和指针类型。理解这些类型的区别,就像理解现实世界中不同物体的性质一样重要。
值类型
值类型直接包含数据,存储在栈中。它们是从System.ValueType派生的。常见的值类型包括:
| 关键字 | 描述 | 范围 | 默认值 |
|---|---|---|---|
| sbyte | 8位有符号整数 | -128 到 127 | 0 |
| short | 16位有符号整数 | -32,768 到 32,767 | 0 |
| int | 32位有符号整数 | -2,147,483,648 到 2,147,483,647 | 0 |
| long | 64位有符号整数 | -9.2e18 到 9.2e18 | 0L |
| byte | 8位无符号整数 | 0 到 255 | 0 |
| ushort | 16位无符号整数 | 0 到 65,535 | 0 |
| uint | 32位无符号整数 | 0 到 4.2e9 | 0 |
| ulong | 64位无符号整数 | 0 到 1.8e19 | 0 |
| float | 32位单精度浮点数 | ±1.5e-45 到 ±3.4e38 | 0.0F |
| double | 64位双精度浮点数 | ±5.0e-324 到 ±1.7e308 | 0.0D |
| decimal | 128位高精度小数 | ±1.0e-28 到 ±7.9e28 | 0.0M |
| char | 16位Unicode字符 | U+0000 到 U+ffff | '\0' |
| bool | 布尔值 | true 或 false | false |
有趣的事实:为什么float用F后缀,double用D后缀,decimal用M后缀?F代表Float,D代表Double,而M代表Money(钱),因为decimal最适合处理财务数据!
引用类型
引用类型不直接包含数据,而是存储对数据的引用(地址)。它们存储在堆中。主要的引用类型包括:
- Object类型:所有类型的基类
- String类型:字符串
- Dynamic类型:动态类型,运行时解析
指针类型
指针类型用于不安全代码,直接操作内存地址。在大多数C#应用中很少使用,除非需要高性能或与非托管代码交互。
类型转换
C#中类型转换有两种:隐式转换(自动)和显式转换(强制)。
隐式转换:当目标类型能安全容纳源类型时自动发生
int i = 10;
double d = i; // 隐式转换,安全
显式转换:当可能存在数据丢失时需要强制转换
double d = 10.5;
int i = (int)d; // 显式转换,结果为10,小数部分丢失
对于不兼容类型,使用Convert类:
string s = "123";
int i = Convert.ToInt32(s);
2.4. 运算符:C#的调味料
算术运算符
| 运算符 | 描述 | 例子 |
|---|---|---|
| + | 加法 | a + b |
| - | 减法 | a - b |
| * | 乘法 | a * b |
| / | 除法 | a / b |
| % | 取模 | a % b |
| ++ | 自增 | a++ 或 ++a |
| -- | 自减 | a-- 或 --a |
注意:++a(前置)先增加后使用,a++(后置)先使用后增加。在性能关键代码中,前置通常比后置更高效,因为后置需要创建临时变量。
关系运算符、逻辑运算符、位运算符
这些运算符用于比较值、进行逻辑操作和位级操作。熟练掌握它们就像厨师熟练使用各种调料一样,能让代码更加精准、高效。
赋值运算符
除了基本赋值(=),C#还提供复合赋值运算符如+=, -=, *=, /=等,让代码更简洁。
特殊运算符
- sizeof():获取类型大小
- typeof():获取类型信息
- ?: 三元条件运算符
- ?? 空值合并运算符
- ?. 空值条件运算符
- => Lambda运算符(用于创建匿名函数)
2.5. 特殊字符:C#的暗语
转义字符
使用反斜杠()改变字符的正常含义:
- \n:换行
- \t:制表符
- ":双引号
- \:反斜杠
- 等等
@符号
在字符串前加@,取消转义字符的特殊含义,保留原始格式:
string path = @"C:\Users\Documents\file.txt"; // 不需要双反斜杠
字符串插值
使用$符号,让字符串内部直接嵌入变量:
string name = "张三";
int age = 25;
Console.WriteLine($"你好,{name}! 你今年{age}岁了。");
比传统的字符串拼接或占位符更直观、易读。
2.6. 可空类型:给值类型赋予"无"的能力
值类型如int、bool等,默认不能为null。但有时候我们确实需要表示"无值"状态。C#提供了可空类型:
int? nullableInt = null; // ?表示可空
bool? flag = null;
// 空值合并运算符
int value = nullableInt ?? 0; // 如果nullableInt为null,则使用0
3. 流程控制的艺术
编程不只是定义变量和方法,还需要控制程序的执行流程。C#提供了丰富的流程控制语句,让你能够指挥代码如指挥交响乐团。
3.1. 选择结构:人生的十字路口
if/else语句
if (temperature > 30)
{
Console.WriteLine("今天真热!");
}
else if (temperature > 20)
{
Console.WriteLine("天气真宜人。");
}
else
{
Console.WriteLine("有点冷,多穿点。");
}
switch语句
C# 8.0+的switch表达式更加简洁:
string GetWeatherDescription(int temperature) =>
temperature switch
{
> 30 => "真热",
> 20 => "宜人",
> 10 => "有点凉",
_ => "真冷" // 默认情况
};
三目运算符
当if/else太重,而你又只需要简单条件赋值时:
string weather = temperature > 25 ? "热" : "不热";
3.2. 循环结构:编程中的复读机
for循环
当你知道需要循环的确切次数时:
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"第{i+1}次循环");
}
while和do-while循环
当你不确定循环次数,只知条件时:
// 先判断,后执行
while (condition)
{
// 循环体
}
// 先执行,后判断
do
{
// 循环体
} while (condition);
循环控制
- break:立即终止整个循环
- continue:跳过当前迭代,进入下一次循环
- return:从方法中返回,终止所有循环
警告:避免在多层嵌套循环中过度使用break,它可能会让代码逻辑变得难以追踪。有时候,将循环重构为方法,用return代替break会更清晰。
4. 复杂数据类型
当你需要处理更复杂的数据结构时,C#提供了多种选择。
4.1. 字符串:不可变的字符序列
C#中的字符串(String)是引用类型,且不可变(每次修改都会创建新字符串)。这看似低效,但.NET的字符串池化机制使其非常高效。
常用字符串操作:
- 拼接:
+或string.Concat() - 格式化:
string.Format()或 字符串插值 - 比较:
==、Equals()或Compare() - 搜索:
IndexOf()、Contains()、StartsWith()、EndsWith() - 修改:
Substring()、Replace()、Trim()、ToUpper()/ToLower()
有趣的事实:字符串不可变性带来了线程安全和哈希码缓存等好处,虽然在频繁修改字符串时可能需要使用
StringBuilder。
4.2. 数组:相同类型的有序集合
C#中的数组是引用类型,具有固定大小:
// 声明和初始化
int[] numbers = new int[5]; // 长度为5的整数数组
string[] names = { "张三", "李四", "王五" }; // 初始化时指定元素
// 多维数组
int[,] matrix = new int[3, 3]; // 3x3二维数组
int[,,] cube = new int[2, 3, 4]; // 三维数组
// 交错数组(数组的数组)
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2];
jaggedArray[1] = new int[3];
jaggedArray[2] = new int[4];
4.3 枚举:命名的整数常量
枚举让代码更具可读性,避免"魔法数字":
enum Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
Weekday today = Weekday.Friday;
Console.WriteLine($"今天是{today}"); // 输出:今天是Friday
// 使用整数值
Console.WriteLine((int)today); // 输出:4(从0开始)
4.4. 结构体:轻量级的类
结构体(struct)是值类型,适合表示轻量级数据结构:
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
public double DistanceToOrigin()
{
return Math.Sqrt(X * X + Y * Y);
}
}
Point p = new Point(3, 4);
Console.WriteLine(p.DistanceToOrigin()); // 输出:5
与类相比,结构体:
- 是值类型,存储在栈上(除非装箱)
- 不支持继承
- 适合小型、不可变的数据
- 默认构造函数不可重写
5. 方法与函数
方法是执行特定任务的代码块,是C#程序的基本构建单元。
5.1. 方法定义
[访问修饰符] [static] 返回类型 方法名(参数列表)
{
// 方法体
return 返回值; // 如果返回类型不是void
}
示例:
public static int Add(int a, int b)
{
return a + b;
}
5.2. 参数传递
C#支持多种参数传递方式:
- 值参数(默认):传递值的副本
- 引用参数(ref):传递变量的引用
- 输出参数(out):用于返回多个值
- 参数数组(params):接受可变数量的参数
// ref参数
void Increment(ref int number)
{
number++;
}
// out参数
bool TryParseInt(string input, out int result)
{
return int.TryParse(input, out result);
}
// params参数
int Sum(params int[] numbers)
{
int total = 0;
foreach (int num in numbers)
{
total += num;
}
return total;
}
// 调用
int x = 5;
Increment(ref x); // x变为6
int parsedValue;
bool success = TryParseInt("123", out parsedValue); // parsedValue为123,success为true
int total = Sum(1, 2, 3, 4, 5); // total为15
5.3. 匿名方法与Lambda表达式
C#支持不具名的方法,用于简短的操作或作为委托:
// 匿名方法
Action<string> greet = delegate(string name)
{
Console.WriteLine($"Hello, {name}!");
};
// Lambda表达式
Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
// 带多个语句的Lambda
Func<int, int, int> add = (a, b) =>
{
Console.WriteLine($"Adding {a} and {b}");
return a + b;
};
Lambda表达式极大地简化了函数式编程和LINQ查询,是现代C#编程的核心特性。
6. 面向对象编程
C#是面向对象的语言,一切皆对象。理解OOP原则是掌握C#的关键。
6.1. 类与对象
类是蓝图,对象是实例:
public class Car
{
// 字段(通常私有)
private string _model;
private int _year;
// 属性(提供对字段的受控访问)
public string Model
{
get { return _model; }
set { _model = value; }
}
// 自动实现的属性(C# 3.0+)
public string Color { get; set; }
// 构造函数
public Car(string model, int year)
{
_model = model;
_year = year;
}
// 方法
public void StartEngine()
{
Console.WriteLine($"{_model}'s engine is starting...");
}
}
// 创建对象
Car myCar = new Car("Tesla Model S", 2023);
myCar.Color = "Red";
myCar.StartEngine();
6.2. 封装:数据保护的艺术
封装是隐藏对象的内部状态,只通过公共方法暴露必要的功能。通过访问修饰符实现:
| 修饰符 | 访问级别 |
|---|---|
| public | 任何地方 |
| private | 仅在类内部(默认) |
| protected | 类内部和派生类 |
| internal | 同一程序集内 |
| protected internal | 同一程序集内或派生类 |
属性是封装字段的最佳实践:
private decimal _salary;
public decimal Salary
{
get { return _salary; }
set { _salary = value >= 0 ? value : 0; } // 验证逻辑
}
6.3. 继承:代码复用的利器
继承允许一个类(子类)获取另一个类(父类)的特性:
public class Vehicle
{
public string Brand { get; set; }
public void Start()
{
Console.WriteLine($"{Brand} vehicle is starting");
}
}
public class Car : Vehicle // Car继承自Vehicle
{
public int NumberOfDoors { get; set; }
// 重写父类方法
public new void Start()
{
base.Start(); // 调用父类方法
Console.WriteLine("Car engine is warming up");
}
}
里氏替换原则
子类应该能替换父类而不改变程序行为。C#提供is和as操作符进行类型检查和安全转换:
Vehicle myVehicle = new Car();
if (myVehicle is Car)
{
Car myCar = (Car)myVehicle; // 显式转换
// 或者
Car myCar2 = myVehicle as Car; // 安全转换,失败返回null
}
6.4. 多态:一物多形
多态允许不同类的对象对同一消息做出响应。C#通过三种方式实现多态:
- 虚方法:父类定义虚方法,子类可选择重写
- 抽象类:强制子类实现特定方法
- 接口:定义合同,实现类必须遵守
// 虚方法
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a generic shape");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
// 抽象类
public abstract class Animal
{
public abstract void MakeSound(); // 没有实现
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Woof!");
}
}
// 接口
public interface ILoggable
{
void Log(string message);
}
public class DatabaseLogger : ILoggable
{
public void Log(string message)
{
// 将消息记录到数据库
}
}
6.5. 特殊类类型
C#提供了一些特殊的类类型来满足特定需求:
-
部分类(partial):将一个类的定义分散在多个文件中
public partial class MyClass { /* 部分1 */ } public partial class MyClass { /* 部分2 */ } -
密封类(sealed):禁止被继承
public sealed class FinalClass { /* 不能被继承 */ } -
静态类(static):不能实例化,只能包含静态成员
public static class MathHelper { public static double Square(double x) => x * x; }
6.6. 关键字:this与base
-
this:引用当前实例
public class Person { private string name; public Person(string name) { this.name = name; // 区分字段和参数 } } -
base:访问基类成员
public class Child : Parent { public Child() : base() // 调用父类构造函数 { base.SomeMethod(); // 调用父类方法 } }
7. 写在最后
C#是一门强大而优雅的语言,它结合了C++的强大和Java的简洁,同时增添了独特的现代特性。从基础语法到面向对象编程,再到高级功能如LINQ、异步编程等,C#提供了一个完整的工具箱来解决各种编程挑战。
记住,成为一名优秀的C#开发者不在于记住所有语法,而在于理解核心概念和设计原则。当你面对问题时,不要问"在C#中如何实现这个",而要问"如何用面向对象的方式解决这个问题"。
祝你在C#之旅中一路顺风!记住,每个大师都曾是初学者,每个bug都是成长的机会。编码愉快!
💬 评论
评论系统接入中...