Thread/ThreadPool/Task/TaskFactory
1、进程和线程的区别
进程是正在执行中的程序,进程中执行的每个任务即为一个线程;
线程属于进程;线程离不开委托;
NetFramework 1.0版本:
线程Thread是C#语言对操作计算机线程的一个封装类;
线程代码讲解
Thread thread = new Thread ();
thread.Suspend();//暂停
thread.Resume();//让暂停的线程继续执行
thread.Abort();//终止线程
thread.ResetAbort();//终止的线程继续执行;静态方法;
Thread.Sleep();//等待
thread.Join(2000);//限时等待,过时不候;
while(thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(2000);
}
//线程的优先级
thread.Priority = ThreadPriority.Highest;//最好不要通过这种方式设置;只是从概念上增加优先级,计算机整体的具体优先级还是以实际为准;
thread.IsBackgroud = true;//设置后台线程;当进程关闭,线程随之取消;
2、单线程
按照顺序一个一个执行
3、多线程
同时工作 同时工作 同时工作 同时工作 同时工作 同时工作
1.效率高
2.消耗资源高
3.各线程执行顺序 难以控制
4、多线程回调
如何控制 多线程 的执行顺序 --> 回调 (多线程实例:线程 2 需要使用 线程 1 中的结果)
//Thread中没有控制线程执行顺序的方法;但可以自己通过封装的方式实现线程回调
//多线程实例:线程 2 需要使用 线程 1 中的结果;
private void main()
{
//1.普通多线程按顺序执行
ThreadStart threadStart = new ThreadStart (() =>
Console.WriteLine("线程1");
);
Action action = () => Console.WriteLine("线程2");
This.ThradWithCallBack(threadStart, action);
//2.如果需要有返回值呢?
Func<int> func= () => //定义了一个委托
{
return DateTime.Now.Year;
};
//int iResult = ThradWithReturn(func);
Func<int> fResult = ThradWithReturn(func);
int iResult = fResult.Invoke();
}
//使用自定义封装方式老进行多线程回调
private void ThradWithCallBack(ThreadStart threadStart,Action action)
{
//开启一个线程去执行传进来的两个线程;
ThreadStart start = () =>{
threadStart.Invoke();//去执行线程
action.Invoke();
};
new Thread(start).Start();
}
private Func<T> ThradWithReturn<T>(Func<int> func)
{
T t = default(T);
ThreadStart thradStart = () =>
{
t = func.Invoke();
}
ThreadStart threadStart = new ThreadStart (thradStart);
threadStart.Start();
return new Func<T>(() =>
{
//threadStart.join(1000);//可以添加限时等待,
return t;
});
}
5、线程池 ThreadPool
线程池中有很多创建好的线程,用的时候拿出来用,用完了或者不用了,就放回线程池;
private void main()
{
Console.WriteLine("线程1-1");
//1.普通多线程按顺序执行
{
WaitCallback waitCallback = new WaitCallback( o =>
Console.WriteLine("线程2-1");
)
ThreadPool.QueueUserWorkItem(waitCallback,"线程池");//从线程池中获取线程
}
//2.回调(线程等待)
{
ManualResetEvent mre = new ManualResetEvent(false);//设置中间变量mre的默认值为false
ThreadPool.QueueUserWorkItem(o =>
{
Console.WriteLine("线程o")
mre.Set();
});//从线程池中获取线程
//线程等待
mre.WaitOne();//等待上一个线程先执行;
}
Console.WriteLine("线程1-1");
}
6、Task
6.1 Task与TaskFactory 的区别?
Task是对线程的一个封装类,实际上是一个线程
TaskFactory :创建Task的地方;
总结:Task来自于ThreadPool
private void main()
{
Console.WriteLine("线程1-1");
//1.如何申请一个线程
{
Task task = new Task(()=>{
Console.WriteLine("线程2");
});
task.Start();
}
{
TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() => {
Console.WriteLine("线程3");
});
}
{
//申请线程
ThreadPool.SetMaxThreads(8, 8);//设置最大线程数量,【全局的】;
List<int> thradIds= new List<int>();
List<Task> tasklist = new List<Task>();
for(int i=0; i<50; i++)
{
tasklist.Add(Task.Run(() => {//申请一个线程
thradIds.Add("获取当前线程Id");
Console.WriteLine("当前线程Id");
}))
}
Task.WaitAll(tasklist.ToArray());//阻塞主线程,等待所有子线程完成任务后,主线程再继续;(会导致主界面卡顿)
int iResult = threadIds.Distinct().Count();
//iResult=8;------------------------
//总结:Task来自于ThreadPool--------
}
{
List<Task> tasklist = new List<Task>();
//单线程进行讲课
Teach("课程1");//Teach() 用户自定义方法
Teach("课程2");
Teach("课程3");
Teach("课程4");
Console.WriteLine("课程教学完毕,准备分配任务编写代码...");
//讲完课之后,让多个人同时去编码(多线程)
TaskFactory taskFactory = Task.Factory();
tasklist.Add(taskFactory.StaretNew(() => Coding("同学1","后台管理")));//Coding()用户自定义方法;
tasklist.Add(taskFactory.StaretNew(() => Coding("同学2","前台页面")));
tasklist.Add(taskFactory.StaretNew(() => Coding("同学3","微服务架构的搭建")));
tasklist.Add(taskFactory.StaretNew(() => Coding("同学4","小程序接口实现")));
//所有同学都顺利完成,我们准备聚个餐,K个歌...
{
//1.Task.WaitAll()
Task.WaitAll(tasklist.ToArray());//等待所有同学完成工作后,主线程才继续执行;会造成主线程中界面卡顿;
Console.WriteLine($"所有项目搭建完毕,我们准备聚个餐,K个歌...");
}
{
//2.taskFactory.ContinueWhenAll()
//使用了回调,在一堆任务执行完毕之后,去申请一个新的线程来执行新的动作;
taskFactory.ContinueWhenAll(tasklist.ToArray(),t => Console.WriteLine($"所有项目搭建完毕,我们准备聚个餐,K个歌..."));
}
{
//3.taskFactory.ContinueWhenAny()当执行了某一个任务时,主线程就会继续执行了;
taskFactory.ContinueWhenAny(tasklist.ToArray(),t => Console.WriteLine($"当某一个同学完成工作了,老师就准备环境的搭建"));
}
{
//4.如何控制线程数量;
//为什么要限制线程数量?因为要合理分配计算机系统资源;
List<Task> tasklist = new List<Task>();
for(int i=0 ; i<10000 ; i++)
{
int k = 1;
if(tasklist.Count(t => t.Status == TaskStatus.Running) >= 20)//如果线程数量为20了,则主线程开始阻塞,且必须等到有一个线程完成之后,再继续主线程
{
Task.WaitAny(tasklist.ToArray());
//下面这句代码自行调试和添加条件;暂未修改
tasklist = tasklist.Where(t => t.Status != TaskStatus.RanToCompletion && t.Status != TaskStatus.Canceled && t.Status != TaskStatus.Faulted).ToList();//保留未执行完毕的线程;RanToCompletion()是否完成;Canceled取消;Faulted异常;
}
//添加一个新的线程
tasklist.Add(Task.Run(()={
Thread.Sleep(2000);
Console.WriteLine($"This is new Thread.");//容易直接卡死计算机;
}));
Console.WriteLine($"线程数量为={tasklist.Count()}");
}
}
{
//5.如果新申请的线程里面有返回值怎么获取呢?
Func<int> func = ()=>{ //定义一个委托delegate,返回当前年份;
Thread.Sleep(3000);
return DateTime.Now.Year;
};
Task<int> task = new Task<int>(func);
task.Start();
int i = task.Result;//这里会阻塞页面
}
}
Console.WriteLine("线程1-1");
}
Task.WaitAll()
与taskFactory.ContinueWhenAll()类似,但是Task.WaitAll()会阻塞主线程,造成卡顿;当所有线程完毕之后,主线程继续;
taskFactory.WaitAny()
主线程进行阻塞,当有一个线程执行完毕后,主线程继续执行;
taskFactory.ContinueWhenAll()
举例:某个主页会涉及到多个数据,数据1来源于DB,数据2来源于第三方,数据3来源于其他DB;
传统顺序:一个一个进行查找,比较耗时;如果每一步平均耗时1秒,那么传统顺序会耗时3秒
使用ContinueWhenAll的时候,系统会进行同时查找,总耗时为1秒;提高查询效率;
taskFactory.ContinueWhenAny()
举例:查找某个数据;
传统思路查找顺序:(1)找缓存 --> (2)找接口 --> (3)找DB
使用ContinueWhenAny的时候,创建了多个线程去不同的地方同时进行查找,当有一个完成查找之后,就可直接返回数据,页面进行相应;
7、表达式目录树 Expression<>
表达式目录树 实际上是一个数据结构(二叉树)。
为什么使用表达式目录树?
答:表达式目录树 – 动态的
初级阶段:数据库查询的时候,都是拼接SQL语句(多一个条件就拼接一个查询字符串);
现在:使用LinqToSql;
Expression<Func<int, int, int>> ex1 = (m, n) => m * n + 2 + 3;//如下图所示
//1.委托
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//(m, n) => m * n + 2实际上是一个匿名方法;Func<返回值, m, n>
var func1 = exp.Compile();
int IResult = func1.Invoke(1, 2);
//2.表达式目录
var peopleQuery = new List<People>().AsQueryable();
Expression<Func<People, bool>> expression = p => p.Id.Tostring().Equals("5");
PeopleQuery.Where(expression);
//进行编译
//通过ILspy反编译
//看中间语言,经过解析后,可以如下表示;
ParameterExperssion p = Experssion.Paramter(typeof(People),"p");
FieldInfo fieldId = typeof(People).GetField("Id");
MemberExperssion exp = Experssion.Field(p,fieldId);
MethodInfo toString = Typeof(People).GetField("Id");
var toString = Experssion.Call(exp ,toString ,Array.Empty<Expression>());
MethodInfo equals = typeof(string).GetMethod("Equals",new Type[] {typeof(string)});
Experssion expressionContant = Expression.Contant("5");
var equalExp = Expression.Call(toStringExp,equals, new Expression[]{
expressionContant
});//equalExp == p.Id.Tostring().Equals("5")
Expression<Func<People, bool>> expression = Expression.Lambda<Func<People, bool>>(equalExp, new ParamterExpression[]{
p
});//expression == p => p.Id.Tostring().Equals("5")
bool bResult = experssion.Compile().Invoke(new People(){Id = 5});//experssion.Compile()定义了一个委托
{
}
7.1 表达式目录树是如何拆解成Sql语句被数据库识别
//ExpressionVisitor//带有Visitor的是访问者模式
//ExpressionVisitor.Visit()方法是访问表达式目录树的入口:1、首先会判断是什么类型的表达式目录(And、GreaterThan);2、会自动的调用到更加专业的方法中去进一步访问(ExpressionVisitor.多个具体分方法名());
//ExpressionVisitor.多个具体分方法名()//转换成SQL能识别的;例1:将 C#中的 && 转换成 SQL 中的 and;例2:程序中需要只允许减号-的存在,一般这个时候,需要我们去重写微软封装的方法,通过人为的判断,将+号全部改成减-号;
我们可以自己定义一个类,来继承自ExpressionVisitor,然后重写封装的方法;
public static main()
{
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//定义了一个表达式目录树;
OperationsVisitor visitor = new OperationsVisitor();
Expression expNew = visitor.Modify(exp);
}
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return this.Visitor(expression);//调用ExpressionVisitor中的Visitor方法;访问表达式目录树的入口
}
//1.重写ExpressionVisitor中的VisitBinary方法
protected override Expression VisitBinary(ExpressionBinary b)//VisitBinary对应ExpressionBinary
{
if(b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left);//递归进入VisitBinary()
Expression right = this.Visit(b.Right);
return Expression.Subtract(left, right);
}
return base.VisitBinary(b);
}
//同理可以重写多个基类中的方法
}
8.async / await 语法糖
async 修饰方法名称的;
主线程遇到await A后就返回了,await A后面的内容由新线程执行;await A的线程遇到await B时又被返回了(await A被放回线程池,表示A活干完了),await B后面的内容被await B执行;总结:线程遇到await A后就返回
程序中要么不用await,要么从头用到尾;
//当程序中没有async时,想用async时;
await Task.CompletedTask();//官方框架推荐
await Task.Delay();
结果:可以增加吞吐量,提升性能;
但是:不是所有的程序都适用async/await;如下
await / async能并发吗?一定能提升性能吗?意义何在?
单个方法内不能并发,线程在遇到await 时就返回了,后续内容由新线程执行,属于串行执行;
不能提升性能,性能会低于同步调用,比如:对于同一个方法,单次处理,是没有性能提升的,只会增加负荷;
意义:1、在多请求并发处理,且资源有限的时候,能增加吞吐量(增加单位时间处理的请求)。
举例:一个孩子进食时间为一小时,**1个老师喂4个孩子(await并行)的时候与和一个老师喂一个孩子(普通串行)**进行比较;await并行时,老师在喂第一个孩子的时候,剩下三个孩子自己进食,然后依次喂食,最终花费时间会小于4小时;普通串行时,老师一个一个喂,最终还是花费4小时;
ILSpy 5.x反编译dll文件,查看源码
ILSpy 中存在状态机,用变量_State表示,状态机模式:一个对象根据不同的状态会有不同的行为(对象只有一个,类似于红绿灯)
Task和await的区别
举例:读本地文件;
Task方式–启动10个线程,分别等着读取文件 ,线程一直处于等待状态,cpu一直被消耗;
await方式–实际上看不到线程,调用系统封装的异步API,发起请求,线程就回去了–由硬件自行读取,读完之后发信号,然后线程池再安排线程去处理;
类似例子:还有调用webApi,WCF,远程数据库链接,连接第三方、读缓存;(都值得使用async)
类似于纯CPU计算类型的(不值得使用async,反而浪费线程资源)
9.反射
9.1反射的原理、反射的使用
9.1.1反射的原理
什么是反射?
答:Reflection命名空间,是微软的一个帮助类库,可以读取metadate元数据,可以使用对应metadate元数据中的方法;
高级语言到机器识别过程:C#高级语言 --> 编译器编译 --> DLL/EXE(又包含了metadate元数据(类似于清单,只能看见方法名)、IL(中间语言)) --> CLR/JIT --> 机器码010101
9.1.2反射的使用
在IOC中的实现;
//基础类
public class SqlServerHelper : IDBHelper
{
public SqlServerHelper()
{
}
public void Query()
{
//查询内容
}
}
public Interface IDBHelper()
{
public void Query();
}
public static IDBHelper CreateInstance()
{
//1.动态加载DLL
Assembly assembly = Assembly.Load("dll名称");//LoadFrom()
//2.获取类型
Type type = assembly.GetType("dll名称.对应的Helper类A");//Helper类A继承IDBHelper
//3.创建对象
object odbHelper = Activator.CreateInstance(Type);
//4.类型转换成对应的Helper
IDBHelper dbHelper = odbHelper.IDBHelper;
//5.调用方法
dbHelper.Query();
}
9.1.3反射的好处和局限
为什么要这么做呢?
1.这样可以断开对细节的依赖(不用添加命名空间引用)
2.实现程序可配置:公司来了技术经理,要求更换数据库为Mysql,按照普通方式写的话,需要改代码,重新编译,重新发布等麻烦步骤;通过反射的话,只需要修改appsetting.json中的即可,不需要重新改代码,重新编译等麻烦步骤,实现了程序可配置;(前提两个数据库的Helper类已经存在了,减少了改代码,编译,发布等步骤)
3.增加了程序扩展性(如果公司只有sqlserver和mysqlhelper,没有orcale数据库的helper类,现在又需要使用orcale数据库,我们最终只需要添加新的orcale project + 改配置文件即可;)
4.实现的就是IOC控制反转的核心思想;
9.1.3.2 局限
9.1.4封装
9.1.2中的程序看起来需要很多代码,所以我们要进行封装一下就好;
public class SimpleCreateInstance()
{
public Static IDBHelper CreateOnstance()
{
//(1)基本封装代码
Assembly assembly = Assembly.Load("dll名称A");//LoadFrom()
Type type = assembly.GetType("dll名称A.对应的Helper类A");//这两个步骤可通过配置文件进行优化,如下
object odbHelper = Activator.CreateInstance(Type);
IDBHelper dbHelper = odbHelper.IDBHelper;
return dbHelper;
//(2)优化封装代码
//core中在appsetting.json通过key value进行配置;
//配置如:"dbHelperRefliction":"dll名称A.对应的Helper类A, dll名称A.dll"
string TypeName = CustomConfigManager.GetConfig("dbHelperRefliction").Split(',')[0]; //dll名称A.对应的Helper类A
string DllName = CustomConfigManager.GetConfig("dbHelperRefliction").Split(',')[1]; //dll名称A
Assembly assembly = Assembly.Load(DllName);//LoadFrom()
Type type = assembly.GetType(TypeName);//这两个步骤可通过配置文件进行优化,如下
object odbHelper = Activator.CreateInstance(Type);
IDBHelper dbHelper = odbHelper.IDBHelper;
return dbHelper;
}
}
public static main()
{
IDBHelper idbHelper = SimpleCreateInstance.CreateInstance();
idbHelper.Query();
}
9.2反射在项目的应用
MVC中的实现:
localhost:8080/Home/Index
为什么浏览器访问这个路径,就可以进入到Action中去呢?最终就是调用Action。
{
//----------------------------
//(1)无参公共方法
Console.WriteLine("反射调用普通方法");
//1.动态加载dll
Assembly assembly = Assembly.LoadForm(@"xxx.dll");
//2.获取类型
Type type = assembly.GetType(@"xxx.dll.类名");
//3.创建对象
object oTest = Activator.CreateInstance(type);
//方法如何调用呢?
//4.获取方法
MethodInfo show1 = type.GetMethod("show1");//show1为自定义的无参方法;
//5.利用Invoke去调用方法
show1.Inoke(oTest,new object[0]);//Inoke需要传输两个参数,所以我们就创建了一个空的对象;new object[0]写法是新的写法;
//结果:成功调用
//-----------------------------
//(2)带参数的公共方法
MethodInfo show2 = type.GetMethod("show2");//show2为自定义的有参方法;show2(int id);
show2.Inoke(oTest,new object[]{123});//成功
show2.Inoke(oTest,new object[]{"ZiFuChuan"});//失败;注意:在传递参数的时候,一定要注意,必须要和方法的参数类型一致;
//-------------------------------
//(3)带参数的公共重载方法
//show3(int id, string name);
//show3(string name, int id);
//show3(int id);
//MethodInfo show3 = type.GetMethod("show3");// 这种方式是有错误的,因为不知道是哪个具体的方法;
MethodInfo show3 = type.GetMethod("show3", new Type[] {typeof(int), typeof(string)});//这样就获取到了show3(int id, string name)方法
show3.Inoke(oTest,new object[]{123, "ZiFuChuan"});//成功
//---------------------
//(4)带参数的**私有**方法
//show4为自定义的有参方法;show4(string name);
//MethodInfo show4 = type.GetMethod("show4");//因为是private,所以这样获取不到的
MethodInfo show4 = type.GetMethod("show4", BindingFlags.NonPublic | BindingFlags.Instance);//传递枚举进去NonPublic,就可以找得到private方法了;
show4.Inoke(oTest,new object[]{"ZiFuChuan"});
//---------------------
//(5)静态方法
MethodInfo show5 = type.GetMethod("show5");//4.获取方法
show5.Inoke(oTest,new object[]{"ZiFuChuan"});//成功
show5.Inoke(null,new object[]{"ZiFuChuan"});//成功。静态方法的调用其实不需要具体的实例,只需要“类型.静态方法名”即可,所以这里不传递实例也是可以的。调用静态方法的基础知识;
//---------------------
//(6)普通类的泛型方法
//show6<T, W, X>(T t, W w, X x)
//普通方法调用方式:对象.Show6<int, string, DateTime>(123, "ZiFuChuan", DateTime.Now);
//普通方法调用方式:对象.Show6(123, "ZiFuChuan", DateTime.Now);这种方法之所以成功是因为语法糖来确定的;
MethodInfo show6 = type.GetMethod("show6");//4.获取泛型方法,成功
//show6.Inoke(oTest,new object[]{123, "ZiFuChuan", DateTime.Now});//失败
MethodInfo showNew6 = show6.MakeGenericMethod(new Type[]{typeof(int), typeof(string), typeof(DateTime)});//MakeGenericMethod指定类型
showNew6.Inoke(oTest,new object[]{123, "ZiFuChuan", DateTime.Now});
//---------------------
//(7)泛型类的泛型方法1
//show7<T, W, X>(T t, W w, X x);;public class A<T, W, X>
Assembly assembly = Assembly.LoadForm(@"xxx.dll");
//这里class为A
//Type type = assembly.GetType(@"xxx.dll.A");//结果:获取不到
Type type = assembly.GetType(@"xxx.dll.A`3");//结果:成功;`3表示泛型,参数有3个;
//object oTest = Activator.CreateInstance(type);//失败,因为通过普通方法 A obj = new A();是不允许的;需要指定类中的泛型;
Type typeNew = type.MakeGenericType(typeof(int), typeof(string), typeof(DateTime));//MakeGenericType指定类型
object oTest = Activator.CreateInstance(typeNew);//成功
MethodInfo show7 = typeNew.GetMethod("show7");//成功,因为是基于类的泛型;这里就不需要再次指定类型了
show7.Inoke(oTest,new object[]{123, "ZiFuChuan", DateTime.Now});
//---------------------
//(8)泛型类的泛型方法2
//show8<T, W, X>(T t, W w, X x);;public class A<T>//+++++++注意这里类的泛型+++++++
Assembly assembly = Assembly.LoadForm(@"xxx.dll");
//这里class为A
Type type = assembly.GetType(@"xxx.dll.A`1");//结果:成功;`1表示泛型,参数有1个;
Type typeNew = type.MakeGenericType(typeof(int), typeof(string), typeof(DateTime));//MakeGenericType指定类型
object oTest = Activator.CreateInstance(typeNew);//成功
MethodInfo show8 = typeNew.GetMethod("show8");//成功的,但是剩下2个泛型类未指定;需要进行指定
show8New = show8.MakeGenericMethod(new Type[]{typeof(int), typeof(DateTime)});//MakeGenericMethod指定类型,方法中剩下的泛型按顺序指定;
show7.Inoke(oTest,new object[]{"ZiFuChuan", 123, DateTime.Now});//成功
}
总:所以实际上在MVC请求网站的时候 ,会一步一步的解析出域名、端口、ControllerName、方法Index;实际上是通过反射的方式定位到具体的位置;
9.3反射封装框架,在线手写ORM框架
9.3.1通过反射操作属性或字段
{
///1.普通方式
People people = new People()
{
Id = 123,
Name = "zifucahun",
Age = 25
};
Console.WriteLine($"{people.Id}");//取值
///2.反射的实现,如下
Type type = typeof(People);
object oPeople = Activator.CreateInstance(type);//创建实例
//赋值
foreach(var prop in type.GetProperties)
{
if(prop.Name.Equals("Id"))
{
prop.SetValue(oPeople, 123);//赋值
}
}
//取值
foreach(var field in type.GetFields())
{
Console.WriteLine($"oPeople.{field.Name} = {field.GetValue(oPeople)}");//遍历取值
}
//反射的好处
//1.通过普通方式无论是赋值还是取值都需要需改代码(当增加、删除、更新一个字段时)
//2.反射取值的时候,就不需要改代码,但是赋值的还是需要修改的;
}
9.3.2 使用反射在ORM框架的实现
public class main
{
SqlServerHelper sqlServerHelper = new SqlServerHelper();
//Student 和Teacher 两个Model对应数据库表;
Student s1 = sqlServerHelper.Query(1);
Teacher t1 = sqlServerHelper.Query(1);
}
public class SqlServerHelper
{
//ORM,面向对象思想。本质:通过对类的操作,实现对数据库的操作(类与数据库的字段和类型必须一致)
Public T Query<T>(int id) //Public T Query<T>(string sqlstr)
{
//1.数据库连接字符串
string conn ="Data Souce=主机名称; Database=数据库名称;User ID=sa;Password=数据库登录密码; MultipleActiveResultSets=True;";
//2.准备SQl语句
string sql = "";//+++++SQL语句可以拆分外部,灵活性更大;++++
Type type = typeof(T);//获取泛型T的类名称;//泛型T类对应数据库表;
object oObj = Activator.CreateInstance(type);//创建泛型T类对应的实例oObj
//3.连接数据库
Using(SqlConnection connection = new SqlConnection(conn))
{
connection.Open();
SqlCommand sqlCommand = new SqlCommend(sql, connection);//将SQL语句连接数据库;
SqlDataReader reader = sqlCommand.ExecuteReader();//准备执行数据读取
reader.Read();//读取数据
//下面使用反射的方式;
foreach(var prop in type.GetProperties())//遍历T的所有属性
{
//给泛型T的实例对象oObj设置属性值,属性值通过从reader索引获取;value = reader[属性名称];
prop.SetValue(oObj,reader[prop.Name]);//注意写法
}
}
return (T)oObj;// return oObj as T;//注意这两种写法的区别
}
//如果只想查询部分字段,可以通过 “特性” 进行过滤;
}