21.using和try finally

都用于资源管理和释放

try-finally 比如文件.数据库连接

using语句处理实现IDisposable接口的对象

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FileStream fileStream = null;
try
{
fileStream = new FileStream("example.txt", FileMode.Open);
// 使用 fileStream 进行文件操作
}
finally
{
if (fileStream != null)
{
fileStream.Dispose();
}
}

c#
1
2
3
4
5
6
using (FileStream fileStream = new FileStream("example.txt", FileMode.Open))
{
// 使用 fileStream 进行文件操作
}
// 在此处 fileStream 自动被释放

用法:能用using就用using,处理未实现IDisposable接口的对象或者自定义复杂资源释放逻辑时,使用try-finally

22.隐式局部变量var

1、只能用于局部变量;2、必须在声明时立即初始化

使用Linq的时候经常用

23.可选参数

可以为参数指定默认值来定义方法的可选参数

c#
1
2
3
4
5
public void ExampleMethod(int mandatoryParam, int optionalParam = 10)
{
Console.WriteLine("Mandatory: " + mandatoryParam + ", Optional: " + optionalParam);
}

扩展

为一个方法定义多个可选参数,需要保证可选参数必须在必选参数之后

指定某个参数的值

c#
1
2
MultipleOptionalParams(5, param3: "Specified");

24.结构体

结构体不能有无参构造函数,原因是:编辑器会自动为每个结构体提供一个隐式的无参构造函数,作用是将结构体成员初始化为其默认值。

防止自定义的和默认提供的构造函数行为不一致,影响初始化逻辑和效率

可以定义有参构造函数

可以用readonly修饰

readonly struct

25.可空类型

声明:?或者Nullable

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int? nullableInt = null;
double? nullableDouble = 3.14;

Nullable<int> a = null;
string? nullableString = null;


if (nullableInt.HasValue)
{
int value = nullableInt.Value;
}
else
{
// 处理没有值的情况
}

//使用GetValueOrDefault方法提供一个默认值
int defaultValue = nullableInt.GetValueOrDefault(0);



26.async关键字

  • async关键字的作用是告诉编译器这个方法可能包含异步操作,在编译时会生成状态机来处理异步操作
  • 加上async关键字意味着方法将返回Task或者Task

27.委托与事件

委托(delegate)是c#定义的一种类型,它可以保存对方法的引用。事件(event)是基于委托的一种类型,专门用于处理发布-订阅模式。

28.阻止方法被子类重写

1、使用sealed关键字对方法进行封闭;只能用于已经被标记为override的方法上;不允许进一步被子类重写时,可以加上sealed关键字

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BaseClass
{
public virtual void MethodToBeOverridden()
{
Console.WriteLine("BaseClass Method");
}
}

public class DerivedClass : BaseClass
{
public sealed override void MethodToBeOverridden()
{
Console.WriteLine("DerivedClass Method");
}
}

//FurtherDerivedClass不能重写MethodToBeOverridden方法
public class FurtherDerivedClass : DerivedClass
{
// This will cause a compile-time error
// public override void MethodToBeOverridden()
// {
// Console.WriteLine("FurtherDerivedClass Method");
// }
}

2、使用private访问修饰符,当一个方法被标记为private时,无法被子类访问也无法被修改

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BaseClass
{
private void MethodNotToBeOverridden()
{
Console.WriteLine("BaseClass Method");
}
}

public class DerivedClass : BaseClass
{
// This will cause a compile-time error
// public override void MethodNotToBeOverridden()
// {
// Console.WriteLine("DerivedClass Method");
// }
}

扩展

当sealed修饰类时,表示该类无法被其它类继承

29.实现索引器

让对象像数组一样被索引;

需要定义一个带有this关键字的属性,它包含get和set访问器来处理索引器操作

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SampleCollection
{
private string[] arr = new string[100];

public string this[int i]
{
get { return arr[i]; }
set { arr[i] = value; }
}
}

//使用索引器
SampleCollection collection = new SampleCollection();
collection[0] = "hello";
Console.Writenln(collection[0]);

30.事件

举例:

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public delegate void NotifyEventHandler(object sender, EventArgs e);
public class Publisher
{
public event NotifyEventHandler Notify;

public void RaiseEvent()
{
Notify?.Invoke(this, EventArgs.Empty);
}
}
public class Subscriber
{
public void OnNotify(object sender, EventArgs e)
{
Console.WriteLine("Event received.");
}
}
public class Program
{
public static void Main(string[] args)
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();

// 订阅事件
publisher.Notify += subscriber.OnNotify;

// 触发事件
publisher.RaiseEvent();
}
}
输出: Event received.

31.回调

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public delegate void CallbackDelegate(string message);

public class DataProcessor
{
public void ProcessData(CallbackDelegate callback)
{
// 处理数据
Console.WriteLine("Processing data...");

// 调用回调方法
callback("Data processing completed.");
}
}

class Program
{
static void Main(string[] args)
{
DataProcessor processor = new DataProcessor();
CallbackMethods callbackMethods = new CallbackMethods();

// 使用委托传递回调方法
processor.ProcessData(callbackMethods.MethodA);

// 更改回调方法
processor.ProcessData(callbackMethods.MethodB);
}
}

public class CallbackMethods
{
public void MethodA(string message)
{
Console.WriteLine($"MethodA: {message}");
}

public void MethodB(string message)
{
Console.WriteLine($"MethodB: {message}");
}
}

32.C#中IOC的三种状态

单例、作用域、瞬态

c#
1
2
3
4
5
6
7
8
9
10
考虑在统一控制器/类同时注入两个实例

singleton:
在该进程中只有一个实例,多次请求返回的都是相同实例

Scoped:
在作用域范围内一个实例,对于webapi或者mvc来说就是一个请求内一个实例,多次请求,每次请求获取的实例不一样,但是在该请求内,获取的实例是一样的

Transient:
注入一次,实例化一次

33.await、async

  • 编译器会为每个异步方法生成一个状态机,状态机维护了异步方法的局部变量和状态
  • 状态机包括一个AsyncTaskMethodBuilder实例,用于管理任务的生命周期,还有一个<>1_state变量,初始状态为-1,表示方法尚未开始执行
  • 调用状态机的Start方法,Start调用MoveNext方法,当遇到await关键字时,它会调用.GetAwait方法获取Await对象,检查该对象的IsCompleted方法,

判断任务是否已经完成,如果完成直接调用GetResult方法获取结果。如果没有完成,则记录当前状态<>1_state,并注册一个回调。

  • 接下来任务完成时,回调会被触发,重新执行MoveNext方法继续执行。当所有异步操作完成,会调用SetResult方法设置任务的结果
  • AsyncTaskMethodBuilder类似的还有SetException方法,Create()创建一个实例,Start()开始执行异步方法,调用MoveNext(),AwaitUnsafeOnCompleted()注册回调函数,当任务完成时触发

异步方法的生命周期:初始化Create,开始执行Start,执行方法体MoveNext,遇到await,方法完成

34.DDD

参考博客

35.Group By

1、group by 可以实现一个最简单的去重查询,假设想看下有哪些员工,除了用 distinct,还可以用:
SELECT name FROM employee_tb1 GROUP BY name;
返回的结果集就是所有员工的名字。
2、group by 语句用法有一个注意点,在 select 语句中,所查询的字段除了聚合函数(SUM ,AVG,COUNT…)以外 必须只能是分组的字段,举例:
SELECT id,name,count() FROM employee_tb1 GROUP BY name;
运行该语句程序会报错,因为 id 字段并不包含在 GROUP BY 分组中,改为如下:
SELECT id,name ,count(
)FROM employee_tb1 GROUP BY id,name;
则程序可以正常运行。
3、分组后的条件使用 HAVING 来限定,WHERE 是对原始数据进行条件限制。几个关键字的使用顺序为 where 、group by 、having、order by ,例如:
SELECT name ,sum() FROM employee_tb1 WHERE id<>1 GROUP BY name HAVING sum()>5 ORDER BY sum(*) DESC;

35.Lock

1、锁对象必须是引用类型,如果是值类型,值类型都会被装箱,每次装箱都会生成一个新的对象,导致锁失效

2、锁对象是私有的,避免外部代码意外锁定同一个对象,导致死锁

3、避免锁的粒度过大过小,过大导致其他线程长时间等待,降低并发性能;过小导致锁频繁的竞争,增加上下文切换的开销

4、避免死锁,如果线程需获取多个锁,确保所有线程以相同的顺序获取锁

36.EFCore的乐观锁

参考博客

37.WPF的MVVM

  • Model:表示应用的数据对象,通常为POCO类(Plain Old CLR Object),不包含UI逻辑。
  • View:XAML文件中的界面定义。WPF提供了强大的数据绑定和事件命令机制,View通常与ViewModel进行绑定。
  • ViewModel:包含与View相关的所有逻辑,通常继承自INotifyPropertyChanged接口,这样View就能监听数据变化并自动更新。

WPF MVVM 示例代码

csharp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Model
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

// ViewModel
public class PersonViewModel : INotifyPropertyChanged
{
private Person _person;
public Person Person
{
get { return _person; }
set { _person = value; OnPropertyChanged(); }
}

public PersonViewModel()
{
Person = new Person() { Name = "John", Age = 30 };
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<Window x:Class="MVVMExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MVVM Example" Height="350" Width="525">
<Grid>
<TextBlock Text="{Binding Person.Name}" />
<TextBlock Text="{Binding Person.Age}" />
</Grid>
</Window>

在这个例子中,ViewModel负责将数据传递给View,而View通过数据绑定自动更新。

Sqlite

SQLite 使用文件级锁,同一时间只允许一个写操作

添加日志

在configureService中配置AddLogging();

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
// 可以添加其他的日志提供者
});
}

// 在Controller或其他服务中使用ILogger
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
_logger.LogInformation("访问主页");
return View();
}
}

配置异常中间件

c#
1
2
builder.Services.AddProblemDetails(); // 注册 ProblemDetails
app.UseExceptionHandler(); // 默认使用 ProblemDetails

IHost

CreateHostBuilder 方法是应用程序启动的核心入口之一,它的本质是 构建和配置应用程序的主机(Host)

IHost之前叫IWebHost,IHost是应用程序的核心运行逻辑,统一托管Web和非Web工作负载,提供配置、DI、日志等基础设施。

asp.net core启动顺序是什么

  1. 程序入口点:应用程序从 Main 方法开始执行。通常,Main 方法会调用 CreateHostBuilder 方法来构建并运行主机。
  2. 创建主机CreateHostBuilder 方法配置主机构建。它应用了 ConfigureWebHostDefaults,设置默认的请求处理管道和服务。
  3. 配置服务:在 Startup 类的 ConfigureServices 方法中,应用配置所需的服务,例如 MVC、数据库上下文等。
  4. 请求处理管道配置:在 Startup 类的 Configure 方法中,定义请求处理管道,如中间件的顺序,例如身份验证、静态文件、路由等。
  5. 应用程序启动:完成配置后,主机会启动,开始接受请求。

StartUp.cs

  • ConfigureServices方法: 用于注册应用服务,如数据库上下文、身份验证、MVC等。
  • Configure方法 : 用于配置HTTP请求处理管道。

数据保护

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
}

public class MyController : Controller
{
private readonly IDataProtectionProvider _dataProtectionProvider;

public MyController(IDataProtectionProvider dataProtectionProvider)
{
_dataProtectionProvider = dataProtectionProvider;
}

public IActionResult ProtectData(string data)
{
var protector = _dataProtectionProvider.CreateProtector("MyPurposeString");
var protectedData = protector.Protect(data);
return Ok(protectedData);
}

public IActionResult UnprotectData(string protectedData)
{
var protector = _dataProtectionProvider.CreateProtector("MyPurposeString");
var unprotectedData = protector.Unprotect(protectedData);
return Ok(unprotectedData);
}
}

使用缓存

内存缓存、分布式缓存、响应式缓存

注册服务

c#
1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache(); // 添加内存缓存服务
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379"; // Redis 配置
});
}

使用EFCore

一、在配置文件中写好数据库连接字符串

二、使用IConfiguration对象读取配置文件的字符串,写一个类继承DbContext,这个类中可以写明DbSet实体映射,重写OnConfiguring方法,里面确定使用的是哪种数据库以及数据库连接字符串以及数据库版本,也可以直接在这里自己声明连接字符串

三、重写OnModelCreating方法,里面去配置数据库实体对应的字段、表等信息,也可以为每个实体类写一个类继承IEntityConfiguration类,在这个类中去扫描当前程序集继承了IEntityConfguration类的类。

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StudentContext : DbContext
{
public DbSet<Student> Students { get; set; }

public StudentContext(DbContextOptions<StudentContext> options)
: base(options)
{
}
}


// ASP.NET Core 6+ 在 Program.cs 中
var builder = WebApplication.CreateBuilder(args);

// 添加DbContext服务
builder.Services.AddDbContext<StudentContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("StudentConnection")));

var app = builder.Build();

分组代替字典

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var categoryGroups = categoryList
.GroupBy(item => GetParentCategory(item))
.Select(g => new { ParentId = g.Key, Count = g.Count() })
.ToList();

var parentIds = categoryGroups.Select(g => g.ParentId).Distinct().ToList();
var categories = await _categoryRepository.Query()
.Where(p => parentIds.Contains(p.Id))
.Select(p => new { p.Id, p.Name })
.ToDictionaryAsync(p => p.Id, p => p.Name);

var simpleCategoryResults = categoryGroups
.Select(g => new SimpleCategoryResult
{
Id = g.ParentId,
Name = categories.GetValueOrDefault(g.ParentId),
Count = g.Count
})
.ToList();

代替左连接

c#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var coupons = await _couponRepository.Query()
.ToListAsync();

var couponIds = coupons.Select(c => c.Id).ToList();
var userCoupons = await _userCouponRepository.Query()
.Where(u => couponIds.Contains(u.CouponId))
.ToListAsync();

var data = coupons.Select(c => new
{
Coupon = c,
RelatedUserCoupons = userCoupons.Where(u => u.CouponId == c.Id),
})
.Select(x => new
{
x.Coupon,
ReceiveTime = x.RelatedUserCoupons.Max(u => u?.ReceiveTime),
UseTime = x.RelatedUserCoupons.Max(u => u?.UseTime),
ReceiveNumber = x.RelatedUserCoupons.Count(u => u?.Status != UserCouponStatus.Used.ToString()),
UsedCount = x.RelatedUserCoupons.Count(u => u?.Status == UserCouponStatus.Used.ToString())
});