目录
前言
一、基本数据类型
精度 : float < double < decimal
动态类型(运行时确定类型) : dynamic
C# 是静态语言(编译时检查数据类型),可以使用dynamic
dynamic obj = new Game();
obj = "str";
Console.WriteLine(obj.GetType());
二、sealed 类与成员
防止类继承、防止派生类重写,可以修饰类或类成员
三、构造
- 先调用基类构造( if exist base.fuMethod ) --> 才是 子类构造
:base(参数?)
继承父类构造,先调用父类构造
四、向上转型和向下转型
- 向上 : 把一个派生类类型 转换为他的基类
- 向下 : 把一个基类转换为某个派生类
转换使用as
关键字 可以防止 抛出异常
Car c = (Car)obj; // 可能会抛出异常,终止程序运行
Car c = obj as Car; // 不会抛出异常
if(c != null){
// 执行后续操作
}
is
关键字 :检查对象类型
if(obj is Car){
Car c = (Car)obj; // 绝对不会出错
}
五、装箱和拆箱
装箱
- 一个值类型转换为引用类型的过程
int i = 10;
object o = i;
// or
object o2 = 10;
- 基本类型变为对象类型
- 值类型变为引用类型
- 保存位置从stack变为heap
- 装箱就是把基本数据使用对象包装起来,一个装箱子的过程
拆箱
- 从对象中打开,提取基本数据类型
object obj = 10;
int num = (int)obj;
使用装箱和拆箱导致
- 类型不安全
- 性能影响
六、虚方法和方法重写
- 虚方法 virtual
- 父类定义方法
- 方法重写 override
- 修改基类方法的执行逻辑,定义子类独有的方法
七、多态
- 父类引用指向子类对象
- 编译看左边,运行看右边
- 方法参数使用接口or父类,传入参数为 接口的实现类or子类 ( 可插拔式,不动源码,只修改实现类or子类
public void ActionHandler(List<Person> persons)
{
foreach (var p in persons)
{
p.SayHello();
}
}
防御性编程
传入类or接口参数 --> 方法内部接收后 先判断方法bool 是否存在or是否已完成 等…
接口与多态
八、抽象类与抽象方法
abstract
- 声明修饰符,修饰类、方法、属性
- 只有声明,没有方法内容
声明为abstract的属性或方法不能有代码实现
某个成员是abstractr修饰,那么class也要使用abstract修饰
派生类必须实现抽象类中声明的所有抽象方法与属性
抽象类不可实例化
九、控制反转与依赖注入
- 将控制创建(new)对象的权力反转交给 容器
- 通过DI 将容器中创建的对象 取出 使用
// 配置IOC
ServiceCollection serviceCollection = new ServiceCollection();
// singleton , 单例模式 ( 整个项目就一个
// scoped , 作用域模式 ( 每一次请求 都是一个作用域 ,请求结束 就结束
// transient , 瞬时模式 ( 一次性
serviceCollection.AddScoped<IService, Service>();
// 从IOC提取服务
ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
IService? service = serviceProvider.GetService<IService>();
// 执行方法
service.DoSomething();
十、Struct
简单版的Class
- class 对象是引用类型,保存在head内存,执行效率低
- struct 值类型,保存stack内存中,运行效率高
一些面向对象特性不能使用,比如继承,不能定义无参构造
十一、泛型
- T 定义在方法上
public class Utility
{ // 判断 IComparable 比较接口
public T FindMax<T>(T a, T b) where T: IComparable
{
return a.CompareTo(b) > 0 ? a : b;
}
}
- T 定义在类上
public class Utility<T> where T : IComparable
{
public T FindMax(T a, T b)
{
return a.CompareTo(b) > 0 ? a : b;
}
}
where T : IComparable
where T : class(product,book)
where T : struct
where T : new()
十二、为空处理
int? i = null; // ?可为空
int normalInt = nullNum ?? 0; // 如果nullNum为空,则被赋值为0
值对象不可为空null
声明值对象,C#编译器会默认赋值
bool => false
整数 => 0
浮点数 => 0
DateTime
值类型,需要特殊处理才能为null , 使用 ??
date ?? DateTime.Today;
十三、拓展方法this
- 不需要继承原Class
- 直接在方法上参数标注
this XX xx
参数 :
this string str
这个string
是拓展的方法
public static class StringExtension
{
public static string ToUpperCase(this string str)
{
return str.ToUpper();
}
public static string ToShortTerm(this string str, int number)
{
return str.Substring(0, number);
}
}
十四、反射 与 元数据
- 反射 :
要实例化对象,但是使用new,不知道目标类型
在运行的过程查看或使用元数据的过程就叫反射- 元数据 :
关于数据的数据 (数据库、文件上的数据,程序本身也是数据)
元数据报存在程序集(Assembly)中:
示例 :
string classLocation = "HelloCSharp.Advanced.MyList"; // 命名空间.类名
Type? objType = Type.GetType(classLocation); // HelloCSharp.Advanced.MyLists
MethodInfo? add = objType.GetMethod("Add"); // Void Add()
object? instance = Activator.CreateInstance(objType); // 创建实例
add.Invoke(instance, null); // 传入对象,对象参数
程序的说明文档:
- 程序有多少类
- 分别保存在什么位置
- 每个类有什么可执行的方法
- 有什么可访问的字段和属性
十五、委托与事件
委托 :
Action<>
无返回值
Func<>
有返回值
事件 :
数据、操作、通知
- 内置事件
定时器,一秒触发一次
private readonly Timer _sendTimer; // 声明定义
_sendTimer = new Timer(1000); // 触发条件
_sendTimer.Elapsed += SendTimer_Elapsed; // 订阅方法
// 方法实现
private async void SendTimer_Elapsed(object sender,ElapsedEventArgs e)
{
await SendDataAsync();
}
当触发条件,即执行订阅方法
- 自定义事件 (有参)
当有数据传递,触发方法
public event EventHandler<(string DeviceId, float[] Data)> DataReceived; //事件定义
// 触发 DataReceived 事件,将数据传递给订阅者
OnDataReceived(dataArr); // 触发条件
// 订阅方法
modbus.DataReceived += (sender, args) => OnDataReceived(args.DeviceId, args.Data);
// 执行订阅方法
private void OnDataReceived(float[] data)
{
DataReceived?.Invoke(this, (_deviceId, data));
}
GPT无参事件示例:
触发事件时要检查是否有订阅者,以避免
NullReferenceException
事件可以携带数据,也可以是无参的
十六、集合Collection
// sort升序
Arrary.Sort(nums);
// Reverse 对数组进行反转
Array.Reverse(nums)
AddRange(添加数组/集合)
InsertRange(插入位置,插入数组/集合)
Insert(位置,插入元素)
RemoveRange(删除位置起始,结束位置) // 删除
RemoveAt(位置)
Remove(某元素)
RemoveAll(Lambda) //符合条件的全部删除
集合.RemoveAll(i=>i=="某元素");
集合.RemoveAll(i=>i.Contains("某元素")); // 删除包含某元素的数据
// 读取数据
集合.Count; // 获取集合当前元素数量
集合.Capacity; // 获取集合当前容量
// 索引器
var num = 集合[1];
-------------------------------------------------------
// 定义集合
var ints = new List<string>()
{
"道格",
"维克",
"格洛弗斯"
};
// 迭代器
List<string>.Enumerator enumerator = ints.GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
// foreach
// 基本类型只能遍历 内部操作集合会报错 ( 只读数据
foreach (var item in ints)
{
Console.WriteLine(item);
}
// 引用类型 可以内部操作 因为改的是地址
foreach (var p in Person)
{
p.Name = "123";
Console.WriteLine(p);
}
//普通for 可遍历,内部操作集合
for (var i = 0; i < ints.Count; i++)
{
Console.WriteLine(ints[i]);
}
十七、 IEnumerable<T>
与 yield return
迭代器与懒加载
示例一 : 使用 IEnumerable 和 yield return 实现自定义过滤器
将偶数一条一条过滤出来
public class NumberFilter
{
public static IEnumerable<int> GetEvenNumbers(IEnumerable<int> numbers)
{
foreach (var number in numbers)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
}
class Program
{
static void Main()
{
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var evenNumber in NumberFilter.GetEvenNumbers(numbers))
{
Console.WriteLine(evenNumber);
}
}
}
示例二 : 对象
public class Game : IEnumerable<Player>
{
private List<Player> Players { get; set; } = new List<Player>();
public Game()
{
Players.Add(new Player(1, "道格", "PC"));
Players.Add(new Player(2, "维克", "Mac"));
Players.Add(new Player(3, "格洛弗斯", "PS5"));
Players.Add(new Player(4, "尤金", "Xbox"));
}
// 使用泛型迭代器输出
public IEnumerator<Player> GetEnumerator()
{
return Players.GetEnumerator();
}
}
// 实体类定义
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
public string device { get; set; }
public Player(int id, string name, string device)
{
Id = id;
Name = name;
this.device = device;
}
}
通常用于需要按需生成、过滤或遍历大量数据的场景
yield return :
懒加载,除非数据要被使用,否则不会创建对象;它不是返回数据,而是数据的迭代。
简单地说,当希望获取一个IEnumerable<T>
类型的集合,而不想把数据一次性加载到内存,就可以考虑使用yield return实现"按需供给"。
static IEnumerable<int> createEnumerable()
{
yield return 1;
yield return 2;
yield return 3;
}
foreach (var i in createEnumerable())
{
Console.WriteLine(i);
}
十八 、字典 Dictionary
键值对
- 基本使用
class Program
{
static void Main()
{
// 创建一个 Dictionary,键为字符串,值为整数
Dictionary<string, int> ages = new Dictionary<string, int>();
// 添加键值对
ages["Alice"] = 30;
ages["Bob"] = 25;
ages["Charlie"] = 35;
// 访问值
Console.WriteLine($"Alice's age: {ages["Alice"]}");
// 检查键是否存在
if (ages.ContainsKey("Bob"))
{
Console.WriteLine($"Bob's age: {ages["Bob"]}");
}
// 更新值
ages["Charlie"] = 36;
Console.WriteLine($"Charlie's updated age: {ages["Charlie"]}");
// 遍历所有键值对
foreach (var kvp in ages)
{
Console.WriteLine($"{kvp.Key} is {kvp.Value} years old.");
}
}
}
TryGetValue
方法,可以安全地尝试获取字典中的值,避免因键不存在而抛出异常
// 创建一个 Dictionary,将国家代码映射到国家名称
Dictionary<string, string> countryCodes = new Dictionary<string, string>
{
{ "US", "United States" },
{ "CA", "Canada" },
{ "JP", "Japan" }
};
// 根据国家代码获取国家名称
string countryCode = "CA";
if (countryCodes.TryGetValue(countryCode, out string countryName))
{
Console.WriteLine($"{countryCode} stands for {countryName}");
}
else
{
Console.WriteLine($"Country code {countryCode} not found.");
}
十九、LINQ
常用的 LINQ 方法 :
where
内部有 yield return 延迟加载,根据条件过滤集合中的元素
select
数据投影,集合中的每个元素投影到新的形式(即映射元素)
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
var lengths = names.Select(name => name.Length);
foreach (var length in lengths)
{
Console.WriteLine(length); // 输出:5, 3, 7
}
Any()
是否存在某元素,返回bool
ALL()
是否所有元素都包括 这个关键字
OrderBy OrderByDescending
:排序
作用:对集合中的元素按升序或降序排序。
// 降序排列Desc
var queryLambda = new DirectoryInfo(path).GetFiles().OrderByDescending(f => f.Length).Take(5);
// 顺序排列ASC
var queryLambda = new DirectoryInfo(path).GetFiles().OrderBy(f => f.Length).Take(5);
GroupBy
:分组 将集合中的元素按指定的键分组
List<string> names = new List<string> { "Alice", "Bob", "Charlie", "Anna", "Bryan" };
var groupedNames = names.GroupBy(name => name[0]); //获取每个元素的第一个字符,作为键key
foreach (var group in groupedNames)
{
Console.WriteLine($"Names starting with '{group.Key}':");
foreach (var name in group)
{
Console.WriteLine(name); // 输出:Names starting with 'A': Alice, Anna
// Names starting with 'B': Bob, Bryan
// Names starting with 'C': Charlie
}
}
示例二 : 分组 + 内部排序 + 只取2条
var queryGroup = cars.GroupBy(c => c.Manufacturer).OrderByDescending(g => g.Key); // Manufacturer制造商作为键
foreach (var g in queryGroup)
{
Console.WriteLine($"{g.Key}有{g.Count()}.");
foreach (var car in g.OrderByDescending(c => c.Combined).Take(2))
{
Console.WriteLine($"{car.Model}{car.Combined}");
}
}
Join
:联接
在两个集合之间执行内连接,类似于 SQL 中的 JOIN 操作。’
class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
class Course
{
public int StudentId { get; set; }
public string CourseName { get; set; }
}
static void Main()
{
List<Student> students = new List<Student>
{
new Student { Id = 1, Name = "Alice" },
new Student { Id = 2, Name = "Bob" },
new Student { Id = 3, Name = "Charlie" }
};
List<Course> courses = new List<Course>
{
new Course { StudentId = 1, CourseName = "Math" },
new Course { StudentId = 1, CourseName = "Physics" },
new Course { StudentId = 2, CourseName = "Chemistry" }
};
var studentCourses = students.Join(courses,
student => student.Id,
course => course.StudentId,
(student, course) => new
{
StudentName = student.Name,
CourseName = course.CourseName
});
foreach (var sc in studentCourses)
{
Console.WriteLine($"{sc.StudentName} is enrolled in {sc.CourseName}");
// 输出:Alice is enrolled in Math
// Alice is enrolled in Physics
// Bob is enrolled in Chemistry
}
}
group join
分组的过程中加入第二个数据源
var qery = manu.GroupJoin(cars, m => m.Name, c => c.Manufacturer,
(m, c) => new { Manufacturer = m, Cars = c })
.OrderByDescending(m => m.Manufacturer.Name);
foreach (var g in qery)
{
Console.WriteLine($"{g.Manufacturer.Name}有{g.Cars.Count()}辆,{g.Manufacturer.Phone}");
foreach (var car in g.Cars.OrderByDescending(c=>c.Combined).Take(2))
{
Console.WriteLine($"\t {car.Model} : {car.Combined}");
}
}
![分组](https://i-blog.csdnimg.cn/direct/12adec35266545eb942c18633bbf5ce7.png)
分组聚合
var query = cars
.GroupBy(car => car.Manufacturer)
.Select(carGroup => new
{
Manufacturer = carGroup.Key,
Avg = carGroup.Average(c => c.Combined),
Max = carGroup.Max(c => c.Combined),
Min = carGroup.Min(c => c.Combined)
});
foreach (var group in query)
{
Console.WriteLine($"{group.Manufacturer} 油耗情况");
Console.WriteLine($" 最高: {group.Max}");
Console.WriteLine($" 最低: {group.Min}");
Console.WriteLine($" 平均: {group.Avg}");
}
First
和 FirstOrDefault
:获取第一个元素
作用:返回集合中的第一个元素。
First 方法在集合为空时抛出异常,
而 FirstOrDefault 在集合为空时返回默认值(如 null)
二十、异步编程
- async 函数本身并不创建异步操作,只有调用await的时候才会执行异步操作。
- 上下文关键字,想要异步调用一个async函数,自己必须也是async函数。
- 返回类型:
void
,Task
,Task<T>
,IAsyncEnumerable<T>
二十二、Dispose
Dispose 可以用来回收比如数据库连接,文件读取,HTTP长连接等无法托管在.NET平台中的外部资源
使用Dispose 必须实现 IDisposable 接口
IDisposable 接口配置using关键字才能完成生命周期的托管
二十三、参数、关键字
out参数与ref参数
out
: 返回方法执行完后的数据
static void Main()
{
int result;
Calculate(10, 20, out result);
Console.WriteLine($"Result is {result}"); // 输出:Result is 30
}
static void Calculate(int a, int b, out int sum)
{
sum = a + b; // 必须在方法内部赋值
}
ref
: 形参在方法中发生的改变,会影响到实参。(形参与实参 连接 ,方法内改变形参,实参也发生变化)
static void Main()
{
int number = 10;
DoubleValue(ref number);
Console.WriteLine($"Doubled value is {number}"); // 输出:Doubled value is 20
}
static void DoubleValue(ref int value)
{
value *= 2; // 修改传入的参数值
}
params可变参数
**作用:**允许向方法传递不定数量的参数。它使得方法可以接收任意数量的相同类型的参数,或者不传递任何参数。
**场景:**当你不确定传递给方法的参数数量时使用。
static void Main()
{
PrintNumbers(1, 2, 3, 4, 5); // 传递多个参数
PrintNumbers(); // 不传递参数
}
static void PrintNumbers(params int[] numbers)
{
foreach (var number in numbers)
{
Console.WriteLine(number); // 输出参数
}
}
全局静态变量
作用: 静态变量属于类,而不是实例。全局静态变量可以在类的所有实例间共享数据。它在类的整个生命周期中都存在。
场景: 当需要在类的不同实例间共享数据,或创建与类关联的常量时使用。
class Program
{
static void Main()
{
Counter.Increment();
Counter.Increment();
Console.WriteLine($"Counter: {Counter.Count}"); // 输出:Counter: 2
}
}
static class Counter
{
public static int Count = 0; // 全局静态变量
public static void Increment()
{
Count++;
}
}
new 关键字
- 创建对象
- 隐藏基类成员 : 当派生类中的成员与基类中的成员同名时,可以使用 new 关键字隐藏基类成员
(隐藏的后果是子类调用不到父类成员,父子同名-> 子类方法上使用)
场景:
在派生类中需要隐藏基类成员时使用
class Program
{
static void Main()
{
DerivedClass derived = new DerivedClass();
derived.Show(); // 输出:This is DerivedClass
}
}
class BaseClass
{
public void Show()
{
Console.WriteLine("This is BaseClass");
}
}
class DerivedClass : BaseClass
{
public new void Show() // 隐藏基类的 Show 方法
{
Console.WriteLine("This is DerivedClass");
}
}
总结
out 和 ref:用于方法参数传递,out 用于返回多个值,ref 用于修改原始变量的值。
params:用于接收不定数量的参数。
全局静态变量:在类的所有实例之间共享数据。
new:用于创建对象实例,或在派生类中隐藏基类成员。
拓展
.sln 与 .csproj
.sln : 解决方案,双击运行
.csproj : 项目文件,包含项目信息,双击运行
字符串
string.join(",",集合) // 集合元素连接方式“,”
反射与文件获取 实例
//将当前工程文件夹与USB地址连接
string USBInterface = Path.Combine(Environment.CurrentDirectory, "USB");
// 地址 :D:\Code\CSharpProjects\HelloCSharp\Computer\bin\Debug\net8.0\USB
// 获取文件夹下的全部文件
var dllFiles = Directory.GetFiles(USBInterface);
var deviceList = new List<IUSB>();
/*
类库编译生成dll
文件夹下获取到的文件包括 :
D:\Code\CSharpProjects\HelloCSharp\Computer\bin\Debug\net8.0\USB\FlashDisk.dll
D:\Code\CSharpProjects\HelloCSharp\Computer\bin\Debug\net8.0\USB\SSD.dll
*/
foreach (var dll in dllFiles)
{
Console.WriteLine("dllFiles :: "+dll);
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(dll);
/*
FlashDisk, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
SSD, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
*/
Console.WriteLine("assembly :: "+assembly);
var typeList = assembly.GetTypes(); // 获取:命名空间.类型
foreach (var type in typeList)
{
/*
FlashDisk.FlashDisk
SSD.SSD
*/
Console.WriteLine("typeList :: "+type);
var interfacesList = type.GetInterfaces(); // 获取类的接口
foreach (var i in interfacesList)
{
if (i.Name == "IUSB")
{
var usb = (IUSB)Activator.CreateInstance(type)!;
deviceList.Add(usb);
}
}
}
}
// 方法执行
foreach (var usb in deviceList)
{
Console.WriteLine("------开始执行USB方法:------ ");
usb.GetInfo();
usb.Read();
usb.Write();
}
生成编译DLL文件
项目名称\bin\Debug\net8.0\USB
Benchmark 性能基准测试
测试 代码 性能 优化