@
目录系统学习下.Net Core。
1.关于.NET
1. 什么是.NET?
.NET是.NET Framework、.NETCore、Xamarin/Mono的统称。
2. 什么是.NET FrameWork?
.NET FrameWork框架只能开发Windows平台的应用程序,也就是说用.NET
FrameWork框架做出来的程序只能运行在Windows系统下。
3. 什么是.NET Core?
.NET Core框架是免费、开源、跨平台的,可以运行在Linux、Macos、Windows平台下。
4.NET Core的优点
- 支持独立部署,不相互影响;
- 彻底模块化;
- 没有历史包袱,运行效率高;
- 不依赖于IIS;
- 跨平台;
- 符合现代开发理念:依赖注入、单元测试等。
2.异步编程
1.C#中async、await关键字
异步方法:用async关键字修饰的方法。
- 异步方法的返回值一般是Task
,T是真正的返回值类型,Task 。惯例:异步方法的名字以Async结尾; - 即使方法没有返回值,也最好把返回值声明为非泛型的Task;
- 调用泛型方法时,一般在方法前加上await关键字,这样拿到的返回值就是泛型指定的T类型;
- 异步方法的”传染性“:一个方法如果await调用,则这个方法也必须修改为async;
- 如果一个方法是异步方法,那么一般在调用这个方法的时候在方法前加await关键字,如:
await File.WriteAllTextAsync(filename,html);
体验下异步编程,如:
public class Program
{
static async Task Main(string[] args)
{
string fileName = @"D:\1.txt";//该行也可以写成string fileName = "D:/1.txt";
File.Delete(fileName);//删除文件
StringBuilder str = new StringBuilder();
for(int i = 0; i < 10000; i++)
{
str.Append(",Hello");
}
File.WriteAllTextAsync(fileName,str.ToString());
string s=await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
}
运行程序会报错,因为File.WriteAllTextAsync(fileName,str.ToString())前没有await关键字,程序运行到此行时,不会跳过此行,会一直执行此行知道此行执行结束,但是由于下一行中的File.ReadAllTextAsync(fileName)前有await,因此运行此代码会报错(报错原因:同一个文件不能同时被一个进程读和另外一个进程写)。
如下图,在File.WriteAllTextAsync(fileName,str.ToString())方法前加一个await关键字就可以了:
public class Program
{
static async Task Main(string[] args)
{
string fileName = @"D:\1.txt";//该行也可以写成string fileName = "D:/1.txt";
File.Delete(fileName);//删除文件
StringBuilder str = new StringBuilder();
for(int i = 0; i < 10000; i++)
{
str.Append(",Hello");
}
await File.WriteAllTextAsync(fileName,str.ToString());
string s=await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
}
2.编写异步方法
1.自定义两个异步方法,一个不带返回值,一个带返回值,如下:
public class Program
{
public static async Task Main(string[] args)
{
await DownloadHtmlAsync1("https://www.youzack.com", @"D:/1.txt");
Console.WriteLine("Ok");
Console.WriteLine("**************");
Console.WriteLine("**************");
Console.WriteLine("**************");
int len2= await DownloadHtmlAsync2("https://www.youzack.com", @"D:/2.txt");
Console.WriteLine("Ok"+len2);
}
/// <summary>
/// 自定义一个异步方法(不带返回值),用于从网页上下载html文件,并写在本地文件中
/// </summary>
/// <param name="url">
/// html文件的下载地址
/// </param>
/// <param name="filename">
/// 下载的html文件保存在本地哪个文件中
/// </param>
/// <returns></returns>
public static async Task DownloadHtmlAsync1(string url, string filename)
{
HttpClient httpClient = new HttpClient();
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(filename, html);
}
/// <summary>
/// 自定义一个异步方法(带返回值),用于从网页上下载html文件,并写在本地文件中
/// </summary>
/// <param name="url">
/// html文件的下载地址
/// </param>
/// <param name="filename">
/// 下载的html文件保存在本地哪个文件中
/// </param>
/// <returns></returns>
public static async Task<int> DownloadHtmlAsync2(string url, string filename)
{
HttpClient httpClient = new HttpClient();
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(filename, html);
return html.Length;
}
}
}
2.如果同样的功能,既有同步方法,又有异步方法,那么优先使用异步方法。
3.异步lambda表达式(委托) 例如:
public class Program
{
public static void Main()
{
ThreadPool.QueueUserWorkItem(async (obj) =>
{
while (true)
{
await File.WriteAllTextAsync(@"D:/3.txt", "sdfffffffffffffffffff");
}
});
Console.ReadKey();
}
}
3.异步方法并不等于多线程
异步方法中的代码并不会自动在新线程中执行,除非把代码放到新线程中执行(通过Task.Run方法将代码放到新的线程中执行)。 看下不使用Task.Run方法和使用Task.Run方法的效果: 不使用Task.Run方法:
public class Program
{
public static async Task Main()
{
Console.WriteLine("之前" + Thread.CurrentThread.ManagedThreadId);
double r = await CalcAsync(5000);
Console.WriteLine("之后" + Thread.CurrentThread.ManagedThreadId);
}
public static async Task<double> CalcAsync(int n)
{
Console.WriteLine("CalcAsync" + Thread.CurrentThread.ManagedThreadId);
double result = 0;
Random random = new Random();
for(var i = 0; i < n * n; i++)
{
result+=random.NextDouble();
}
return result;
}
}
使用Task.Run方法:
public class Program
{
public static async Task Main()
{
Console.WriteLine("之前" + Thread.CurrentThread.ManagedThreadId);
double r = await CalcAsync(5000);
Console.WriteLine("之后" + Thread.CurrentThread.ManagedThreadId);
}
public static async Task<double> CalcAsync(int n)
{
return await Task.Run(() =>
{
Console.WriteLine("CalcAsync" + Thread.CurrentThread.ManagedThreadId);
double result = 0;
Random random = new Random();
for (var i = 0; i < n * n; i++)
{
result += random.NextDouble();
}
return result;
});
}
}
4.为什么有的异步方法没标async
1.async方法的缺点:
- 异步方法会生成一个类,运行效率没有普通方法高;
- 可能会占用非常多的线程; 用async和await配合来调用普通方法(推荐此方式,运行效率高),例如:
public class Program
{
public static async Task Main()
{
string s = await ReadAsync(1);
Console.WriteLine(s);
}
public static Task<string> ReadAsync(int n)
{
if (n == 1)
{
return File.ReadAllTextAsync(@"D:/1.txt");
}else if (n == 2)
{
return File.ReadAllTextAsync(@"D:/2.txt");
}
else
{
throw new ArgumentException();
}
}
}
5.不要用sleep
1.Thread.Sleep()方法阻塞的是当前线程,如果当前线程是主线程,那么调用Sleep方法就会阻塞主线程。如果想在异步方法中暂停一段时间,不要用Thread.Sleep()方法,因为它会阻塞调用线程,而要用await
Task.Delay()。举例:下载一个网址,3秒后下载另外一个。例如,新建一个Winfrom程序(选用winform程序而不采用控制台为例子是因为在控制台中看不到区别,但是在winfrom程序中就可以看到区别,在ASP.NET
Core中也看不到区别,但是Sleep()方法会降低并发,因此不要用Sleep()方法):
在后台的代码如下:
private async void button1_Click(object sender, EventArgs e)
{
HttpClient httpClient=new HttpClient();
string s1= await httpClient.GetStringAsync("https://www.youzack.com");
textBox1.Text = s1.Substring(0, 2000);
//Thread.Sleep(3000);
await Task.Delay(3000);
string s2 = await httpClient.GetStringAsync("https://www.baidu.com");
textBox1.Text = s2.Substring(0, 200);
}
6.CancellationToken
1.有时需要提前终止任务,比如:请求超时、用户取消请求。很多异步方法都有CancellationToken参数,用于获得提前终止执行的信号。
2.CancellationToken结构体:
- bool IsCancellationRequested是否取消
- Register(Action callback) 注册取消监听
- ThrowIfCancellationRequested()如果任务被取消,执行到这句话就抛异常。
3.CancellationTokenSource类- CancelAfter()超时后发出取消信号
- Cancel()发出取消信号
- CancellationToken Token 代码举例1,5秒后请求被取消:
public class Program
{
public static async Task Main(string[] args)
{
//await Download1Async("https://www.youzack.com", 100);
CancellationTokenSource cts=new CancellationTokenSource();
cts.CancelAfter(5000);
CancellationToken cToken=cts.Token;
await Download2Async("https://www.youzack.com",100,cToken);
}
/// <summary>
/// 不带CancellationToken的方法
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <returns></returns>
public static async Task Download1Async(string url,int n)
{
HttpClient client = new HttpClient();
for(int i = 0; i < n; i++)
{
string html=await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
}
}
/// <summary>
/// 带CancellationToken的方法
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <returns></returns>
public static async Task Download2Async(string url, int n, CancellationToken cancellationToken)
{
HttpClient client = new HttpClient();
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("请求被取消");
break;
}
}
}
}
4.在ASP.NET Core开发中,一般不需要自己处理cancellationToken、CancellationTokenSource 这些,只要能做到“能转发cancellationToken”即可。在ASP.NET Core会对用户请求中断进行处理。
7.WhenAll
1.Task类的重要方法:
- Task
WhenAny(IEnumerable tasks)等,任何一个Task完成,Task就完成; - Task<TResult[]> WhenAll
(params Task [] tasks)方法:等所有Task完成,Task才完成,用于等待多个任务执行结束,但是他们不在乎他们的执行顺序。 - Task
FromResult (TResult result)方法:创建普通数值的Task对象。
2.代码练习:计算一个文件夹下,所有文本文件的单词个数汇总。代码如下:
public class Program
{
public static async Task Main(string[] args)
{
string[] files=Directory.GetFiles(@"D:/Test");
Task<int>[] countTasks=new Task<int>[files.Length];
for(int i=0; i<files.Length; i++)
{
string filename=files[i];
Task<int> t = ReadCharsCount(filename);
countTasks[i] = t;
}
int[] counts=await Task.WhenAll(countTasks);
int c = counts.Sum();//计算数组中所有元素的和
Console.WriteLine(c);
}
static async Task<int> ReadCharsCount(string filename)
{
string s=await File.ReadAllTextAsync(filename);
return s.Length;
}
}
8.异步编程中其他问题
1.接口中的异步方法:
async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲是没区别的,因此对于接口中的方法或抽象方法不能修饰为async。
如:
public interface Itest
{
Task<int> GetCharCount(string file);
}
public class Test : Itest
{
public async Task<int> GetCharCount(string file)
{
string s = await File.ReadAllTextAsync(file);
return s.Length;
}
}
2.异步与yield
yield return不仅能够简化数据的返回,而且可以让数据处理"流水线化",提升性能。 如:
public class Program
{
public static async Task Main(string[] args)
{
IEnumerable<string> lists= Test();
foreach(var item in lists)
{
Console.WriteLine(item);
}
}
public static IEnumerable<string> Test()
{
yield return "hello1";
yield return "hello2";
yield return "hello3";
}
}
3.LINQ
1.为什么要学习LINQ?
1.答案是让数据处理变得简单。 比如,现在有这样一个需求:统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。用LINQ的知识很容易就可以写出来:
var items = s.Where(c => char.IsLetter(c)) //过滤非字母
.Select(c => char.ToLower(c)) //大写字母转化为小写
.GroupBy(c => c)//根据字母进行分组
.Where(g => g.Count() > 2)//过滤掉出现次数小于2
.OrderByDescending(g => g.Count())//按照出现次数排序
.Select(g => new { Char = g.Key, Count = g.Count() });
2.要想把LINQ学好,需要按照委托---lambda---LINQ的顺序来,即先把委托学好,再学好委托中lamda,最后学习LINQ。
2.LINQ中的常用扩展方法
LINQ中提供了大量类似于Where的扩展方法,简化数据处理,大部分都在System.Linq命名空间中。
1.Where方法:根据条件执行操作
2.Count方法:获取数据的条数
3.Any方法:是否至少有一条数据
如:
bool b1 = List.Any(e => e.Salary > 8000);
bool b2 = List.Where(e => e.Salary > 8000).Any();
4.获取一条数据(是否带有参数的两种写法)
- Single:有且只有一条满足要求的数据;
- SingleOrDefault:最多只有一条要满足的数据;
- First:至少有一条,返回第一条;
- FirstOrDefault:返回第一条或默认值;
5.排序
- Order() 升序排序;
- OrderByDescending() 倒序排序;
- list.OrderBy(e => e.Age);//根据Age来对集合进行升序排序; 对于简单类型排序,也许不用lambda表达式。 特殊案例:按照最后一个字符排序;用Guid或者随机数进行随机排序。
5.多规则排序
可以在Order()、OrderByDescending()后继续写ThenBy () 、ThenByDescending()。
案例:优先按照Age排序,如果Age相同再按照Salary排序。 list.OrderBy(e =>
e.Age).ThenByDescending(e => e.Salary), 千万不要写成list.OrderBy(e =>
e.Age). OrderByDescending (e => e.Salary)。
6.限制结果集,获取部分数据
Skip(n)跳过n条数据,Take(n)获取n条数据。 案例:跳过前2条数据并开始获取之后的3条数据(即获得第3、4、5条数据): var
orderedItems1 = list.Skip(2).Take(3); Skip()、Take()也可以单独使用。
7.聚合函数
Max()、Min () 、Average () 、Sum () 、Count ()。
LINQ中所有的扩展方法几乎都是针对IEnumerable接口的,而几乎所有能返回集合的都返回IEnumerable,所以是可以把几乎所有方法“链式使用”的。
list.Where(e => e.Age > 30).Min(e=>e.Salary)。 代码举例:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
int maxAge = list.Max(e => e.Age);
Console.WriteLine($"最大年龄:{maxAge}");
long minId = list.Min(e => e.Id);
Console.WriteLine($"最小Id:{minId}");
double avgSalary = list.Average(e => e.Salary);
Console.WriteLine($"平均工资:{avgSalary}");
int sumSalary = list.Sum(e => e.Salary);
Console.WriteLine($"工资总和:{sumSalary}");
int count = list.Count();
Console.WriteLine($"总条数:{count}");
int minSalary2 = list.Where(e => e.Age > 30).Min(e => e.Salary);
Console.WriteLine($"大于30岁的人群中的最低工资:{minSalary2}");
}
record Employee
{
public long Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool Gender { get; set; }
public int Salary { get; set; }
}
运行结果:
8.分组GroupBy方法
GroupBy()方法参数是分组条件表达式,根据条件将一个大点的集合分成多个小集合。 例子:根据年龄分组,获取每组人数、最高工资、平均工资:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
var items = list.GroupBy(e => e.Age);
foreach (var item in items)
{
int age = item.Key;
int count = item.Count();
int maxSalary = item.Max(e => e.Salary);
double avgSalary = item.Average(e => e.Salary);
Console.WriteLine($"年龄{item.Key},人数{count},最高工资{maxSalary},平均工资{avgSalary}");
}
}
9.投影select方法
通过Select方法把集合中的每一项转换为另外一种类型。 IEnumerable
ages = list.Select(e =>
e.Age); IEnumerablenames = list.Select(e=>e.Gender?"男":"女");
var dogs = list.Select(p=>new Dog{NickName=e.Name,Age=e.Age}); 代码如下:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
var ages = list.Select(e => e.Age);
Console.WriteLine(string.Join(",", ages));
var names = list.Select(e => e.Gender ? "男" : "女");
Console.WriteLine(string.Join(",", names));
var items1 = list.Select(e => new { e.Name, e.Age, XingBie = e.Gender ? "男" : "女" });
foreach (var item in items1)
{
string name = item.Name;
int age = item.Age;
string xingbie = item.XingBie;
Console.WriteLine($"名字={name},年龄={age},性别={xingbie}");
}
var items2 = list.GroupBy(e => e.Gender).Select(g => new
{
Gender = g.Key,
Count = g.Count(),
AvgSalary = g.Average(e => e.Salary),
MinAge = g.Min(e => e.Age)
});
foreach (var item in items2)
{
Console.WriteLine($"性别{item.Gender},人数{item.Count},平均工资{item.AvgSalary:F},最小年龄{item.MinAge}");
}
}
运行结果:
10.匿名类型
var p = new {Name="tom",Id=1}; var p1 = new {name,Id=1,p.Age};
通过反编译看匿名类型原理。
11.投影与匿名类型(很常用)
var items = list.Select(e=>new {e.Name,e.Age,XingBie= e.Gender ? "男" : "女"});
var items = list.GroupBy(e => e.Gender).Select(g=>new { Gender=g.Key,Count=g.Count(),AvgSalary= g.Average(e => e.Salary),MinAge= g.Min(e => e.Age)});
3.LINQ中的链式调用
1.集合转换
有一些地方需要数组类型或者List类型的变量,我们可以用ToArray()方法和ToList()分别把IEnumerable
转换为数组类型和List 类型。
2.链式调用
Where、Select、OrderBy、GroupBy、Take、Skip等返回值都是IEnumerable
类型,所以可以链式调用。例子:“获取Id>2的数据,然后按照Age分组,并且把分组按照Age排序,然后取出前3条,最后再投影取得年龄、人数、平均工资”。
代码举例:
public static void Main()
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 9000 });
var items = list.Where(e => e.Id > 2).GroupBy(e => e.Age).OrderBy(g => g.Key).Take(3)
.Select(g => new { Age = g.Key, Count = g.Count(), AvgSalary = g.Average(e => e.Salary) });
foreach (var item in items)
{
Console.WriteLine($"年龄:{item.Age},人数:{item.Count},平均工资:{item.AvgSalary}");
}
运行结果:
4.LINQ中小知识补充
1.过滤掉字符串中的非字母(即只保留字符串中的字母)
例如,有如下这样一段字符串:
sdfgsdgdffffreqqwertyuiopasdfghjklzxcvbnm,yhndhsfdsgfeybxnzzzzzzzzcyweferoqweyrei./,\]-=sadjsfhjdsfjuewxbcvc
现在要把所有的非字母过滤掉,可以很方便地用LINQ中的方法实现:
string str = @"sdfgsdgdffffreqqwertyuiopasdfghjklzxcvbnm,yhndhsfdsgfeybxnzzzzzzzzcyweferoqweyrei./,\]-=sadjsfhjdsfjuewxbcvc";
var result = str.Where(c => char.IsLetter(c));//选择字母,即过滤掉非字母
foreach(var item in result)
{
Console.Write(item+" ");
}
5.LINQ练习题
习题1:
有一个用逗号分隔的表示成绩的字符串,如"61,90,100,99,18,22,38,66,80,93,55,50,89",计算这些成绩的平均值。
public class Program
{
static void Main(string[] args)
{
List<int> lists = new List<int>() { 61, 90, 100, 99, 18, 22, 38, 66, 80, 93, 55, 50, 89 };
var results = lists.Average();//求出集合中的平均数
Console.WriteLine(results);
}
}
习题2: 统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。
public class Program
{
static void Main(string[] args)
{
string str = @"sdfgsdgdffffreqqwertyuiopasdfghjklzxcvbnm,yhndhsfdsgfeybxnzzzzzzzzcyweferoqweyrei./,\]-=sadjsfhjdsfjuewxbcvc";
var result = str.Where(c => char.IsLetter(c))//选择字母,即过滤掉非字母
.Select(c => char.ToLower(c))//大写字母全部转换为小写
.GroupBy(c => c)//根据字母进行分组
.Where(c => c.Count() > 2)//过滤掉字母出现频率低于2的
.OrderByDescending(c => c.Count())//根据出现次数降序排序(即出现次数多的排在前面)
.Select(c => new { Key = c.Key, Count = c.Count() });//实例化一个匿名对象,并赋值字符的值和出现的次数
foreach(var item in result)
{
Console.WriteLine(item);
}
}
}
4.依赖注入
1.依赖注入与控制反转的概念
- 生活中的“控制反转”:自己发电和用电网的电。
- 依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式。
- 依赖注入作用:简化模块的组装过程,降低模块之间的耦合度。
- 代码控制反转的目的:“怎样创建XX对象”→“我要XX对象” 两种实现方式: - 服务定位器(ServiceLocator); - 依赖注入(Dependency Injection,DI);
- 畅想Demo 两种方式代码比较:
- 服务定位器csharp IDbConnection conn = ServiceLocator.GetService<IDbConnection>();
- 依赖注入
class Demo
{
public IDbConnection Conn { get; set; }
public void InsertDB()
{
IDbCommand cmd = Conn.CreateCommand();
}
}
2.NET中DI服务注册
1.DI的几个概念
服务(service):对象; 注册服务; 服务容器:负责管理注册的服务; 查询服务:创建对象及关联对象;
对象生命周期:Transient(瞬态); Scoped(范围); Singleton(单例);
2.NET中使用DI
1.根据类型来获取和注册服务。
2.可以分别指定服务类型(service type)和实现类型(implementation type)。这两者可能相同,也可能不同。服务类型可以是类,也可以是接口,建议面向接口编程,更灵活。
3.NET控制反转组件取名为DependencyInjection,但它包含ServiceLocator的功能。
4.使用DI的步骤:
- Install-Package Microsoft.Extensions.DependencyInjection
- using Microsoft.Extensions.DependencyInjection
- ServiceCollection用来构造容器对象IServiceProvider 。调用ServiceCollection的BuildServiceProvider()创建的ServiceProvider,可以用来获取BuildServiceProvider()之前ServiceCollection中的对象。
5.一个简单的例子:
using System;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp1
{
public interface ITestService
{
public string Name { get; set; }
public void SayHi();
}
public class TestServiceImpI: ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine($"Hi,I'm{Name}");
}
}
public class TestServiceImpI2 : ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine($"你好,我是{Name}");
}
}
public class Program
{
public static void Main()
{
//ITestService t = new TestServiceImpI();
//t.Name = "tom";
//t.SayHi();
ServiceCollection services = new ServiceCollection();
services.AddTransient<TestServiceImpI>();
using (ServiceProvider sp = services.BuildServiceProvider())
{
TestServiceImpI t= sp.GetService<TestServiceImpI>();
t.Name = " Lily";
t.SayHi();
};
Console.ReadKey();
}
}
}
3.服务的生命周期
1.给类构造函数中打印,看看不同生命周期的对象创建,使用serviceProvider.CreateScope()创建Scope。
2.如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法。
3.不要在长生命周期的对象中引用比它短的生命周期的对象。在ASP.NET Core中,这样做默认会抛异常。
4.生命周期的选择:如果类无状态,建议为Singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。
5.NET注册服务的重载方法很多,看着文档琢磨吧。
4.服务定位器
标签:Salary,03,string,Core,C#,Age,list,new,public From: https://www.cnblogs.com/hezexi/p/18513390
- IServiceProvider的服务定位器方法:
- T GetService
() 如果获取不到对象,则返回null; - object GetService(Type serviceType);
- T GetRequiredService
()如果获取不到对象,则抛异常; - object GetRequiredService(Type serviceType) ;
- IEnumerable
GetServices ()适用于可能有很多满足条件的服务; - IEnumerable