- A+
1. 什么是泛型
编写一个方法,实现两数相加并返回结果。
作用
-
泛型增强了代码的可读性
-
泛型有助于实现代码的重用、保护类型的安全以及提高性能。
-
我们可以创建泛型集合类。
-
泛型实现了类型和方法的参数化
-
我们还可以对泛型类进行约束以访问特定数据类型的方法。
-
关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
定义
泛型是可以当作任意一种且由编译期间决定其最终类型的数据类型。通俗来讲,泛型,即泛指某种类型。
2. 泛型类
1. 泛型类声明格式
泛型类,将指定类型参数(Type Parameter,通常以T 表示),紧随类名,并包含在<>符号内。
public class 泛型类<T> { /// <summary> /// 泛型属性 /// </summary> public T ItemName { get; set; } public string MyName { get; set; } // 也可定义其他的属性 }
使用泛型类
泛型类<string> obj = new(); obj.ItemName = "任我行码农场"; Console.WriteLine("ItemName的值是:"+obj.ItemName); Console.WriteLine("ItemName的类型是:"+obj.ItemName.GetType());
输出结果:
ItemName的值是:任我行码农场 ItemName的类型是:System.String
3. 泛型方法
泛型方法,将指定类型参数(Type Parameter,通常以T 表示),紧随方法名,并包含在<>符号内。
格式
访问修饰符 方法返回类型 方法名<T>(参数列表) { // 方法体... }
普通类中的泛型
public class MyClass { // 泛型方法 public T Sum<T>(T a, T b) { return (dynamic) a + b; } }
泛型类中的泛型方法
public class 泛型类<T> { /// <summary> /// 泛型属性 /// </summary> public T ItemName { get; set; } public string MyName { get; set; } public void Sum<T>(T a, int b) { Console.WriteLine((dynamic)a+b); } }
4. 泛型约束
1. 为什么要用泛型约束
[Test] public void Test2() { MyClass my = new MyClass(); Student s1 = new Student(1,"张三"); Student s2 = new Student(2,"李四"); my.Sum<Student>(s1, s2); // 合适吗? } record Student(int Id,string Name);
上述代码一定会报错, 两个Student对象不可能可以直接相加!!
此时,如果不对Sum 这个泛型方法加以约束,就很有可能出现上述情况。
所谓泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类或者T必须实现某个接口等。 使用where关键字加上约束
格式如下:
public class 泛型类<T> where T:约束类型 { }
2. 约束的类型
struct | 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型 |
---|---|
class | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 |
new() | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 |
基类名 | 类型参数必须是指定的基类或派生自指定的基类 |
接口名 | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 |
泛型约束--struct
泛型约束中的struct 指定类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型
public class MyClass { // 泛型方法 public T Sum<T>(T a, T b) where T:struct { return (dynamic) a + b; } } [Test] public void Test2() { MyClass my = new MyClass(); Student s1 = new Student(1,"张三"); Student s2 = new Student(2,"李四"); my.Sum<Student>(s1, s2); // 此时编译器直接给出错误提示,编译失败 } record Student(int Id,string Name);
my.Sum<Student>(s1, s2); // 此时编译器直接给出错误提示,编译失败
泛型约束--class
泛型约束class ,指定类型参数必须是引用类型,包括任何类、接口、委托或数组类型。
public interface IRepository<T> where T:class { // 接口也可以有默认实现 int Add(T model) { Console.WriteLine("添加了一条数据"); return 1; } int Update(T model); int Delete(dynamic id); T GetModel(dynamic id); IList<T> GetList(string condition); }
如果有组合约束时,class约束必须放在最前面。
public interface IRepository<T> where T:class,new() // class放前面,否则编译失败 { int Add(T model); int Update(T model); int Delete(dynamic id); T GetModel(dynamic id); IList<T> GetList(string condition); }
测试效果
IRepository<int> repository = new IRepository<int>(); // 编译失败 IRepository<object> repository = new IRepository<object>(); // 编译通过
泛型约束—new()
泛型约束new(),指定类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。加上该约束后可以在类中或者方法中实例化T类型的对象。
public class BaseDAL<T> where T:class,new() //new()放后面 { public List<T> GetList<T>() { List<T> list = new(); T t = new(); // 可以实例化了 list.Add(t); return list; } }
测试效果
BaseDAL<Student> dal = new BaseDAL<Student>(); // 编译失败,Student并未提供无参构造 record Student(int Id,string Name);
泛型约束—基类名
类型约束之基类名称,类型参数必须是指定的基类或派生自指定的基类
public class StudentDal<T> where T:BaseModel { } class BaseModel { }
说明:基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义,因为sealed类没有子类.
泛型约束—接口名称
泛型约束之接口名称,类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
interface IAnimal { // ... } interface IPerson { // ... } class 泛型类<T> where T:IAnimal,IPerson { } class Student:IAnimal,IPerson { } // 测试使用 泛型类<Student> myClass = new 泛型类<Student>(); // 测试通过
5. 泛型协变和逆变
协变(Convariant)和逆变(Contravariant)的出现,使数组、委托、泛型类型的隐式转换变得可能。 子类转换成基类,称之为协变;基类转换成子类,称之为逆变。.NET4.0以来,支持了泛型接口的协变和逆变。
泛型协变
如果子类泛型隐式转换成基类泛型,使用泛型协变
-
先准备好两个子父类
public class Animal { public virtual void Run() { Console.WriteLine("动物在跑"); } } public class Dog:Animal { public override void Run() { Console.WriteLine("狗在跑"); } }
-
定义好泛型协变接口,
public interface IFactory<out T> // out 协变 只能应用于interface { T Create(); }
-
实现协变接口
public class FactoryImpl<T>:IFactory<T> where T:new() { public T Create() { return new T(); } }
-
测试泛型协变
[Test] public void Test3() { IFactory<Dog> iFactory = new FactoryImpl<Dog>(); IFactory<Animal> parentFactory = iFactory; // 协变 Animal animal = parentFactory.Create(); animal.Run();// 输出结果:狗在跑 }
-
泛型接口中的out关键字必不可少
-
out 协变 只能应用于interface
泛型逆变
如果基类泛型隐式转换成子类泛型,使用泛型逆变。
-
关于通知的一个接口
public interface INotification { public string Message { get; } } // 关于通知接口的抽象实现。 public abstract class Notification : INotification { public abstract string Message { get; } }
-
关于通知抽象类的具体实现。
public class MainNotification : Notification { public override string Message => "您有一封新的邮件"; }
-
接下来,需要把通知的信息发布出去,需要一个发布通知的接口INotifier,该接口依赖INotification,大致INotifier<INotification>,而最终显示通知,我们希望INotifier<MailNotification>,INotifier<INotification>转换成INotifier<MailNotification>,这是逆变,需要关键字in。
public interface INotifier<in T> where T : INotification { void Notifier(T notification); }
-
实现INotifier:
public class Notifier<T> : INotifier<T> where T : INotification { public void Notify(T notification) { Console.WriteLine(notification.Message); } }
-
客户端调用
[Test] public void Test4() { INotifier<INotification> notifier = new Notifier<INotification>(); INotifier<MainNotification> mailNotifier = notifier; // 逆变 mailNotifier.Notify(new MainNotification()); }
● INotifier的方法Notify()的参数类型是INotification,逆变后把INotification类型参数隐式转换成了实现类 MailNotificaiton。 ● 泛型接口中的in关键字必不可少
协变逆变总结
逆变与协变只能放在泛型接口和泛型委托的泛型参数里面,在泛型中out修饰泛型称为协变,协变(covariant) 修饰返回值 ,协变的原理是把子类指向父类的关系,拿到泛型中。
在泛型中in 修饰泛型称为逆变, 逆变(contravariant )修饰传入参数,逆变的原理是把父类指向子类的关系,拿到泛型中。
内置的协变逆变泛型
序号 | 类型 | 名称 |
---|---|---|
1 | 接口 | IEnumerable<out T> |
2 | 委托 | Action<in T> |
3 | 委托 | Func<out T> |
4 | 接口 | IReadOnlyList<out T> |
5 | 接口 | IReadOnlyCollection<out T> |
6. 泛型的应用
手写ORM框架
ORM 框架,对象关系映射。
-
从 老师提供utils 文件夹中将DbHelper拷贝至当前你的项目中
-
nuget引用:1:Microsoft.Extensions.Configuration,2:System.Data.SqlClient
-
封装ORM 框架
public class DbContext<T> where T : class, new() { /// <summary> /// 添加功能 /// </summary> /// <param name="t">要添加的对象</param> public void Add(T t) { // insert into Student(.属性1,属性2,属性3..) values(.'属性值1','属性值2','属性值3'..) StringBuilder sql = new StringBuilder($"insert into {typeof(T).Name}("); // 跳过第一个属性 var propertyInfos = typeof(T).GetProperties().Skip(1); var propNames = propertyInfos.Select(p => p.Name).ToList(); sql.Append(string.Join(",", propNames)); sql.Append(") values('"); List<string> values = new List<string>(); foreach (var propertyInfo in propertyInfos) { // 获取属性值 values.Add(propertyInfo.GetValue(t).ToString()); } sql.Append(string.Join("','", values)); sql.Append("')"); DbHelper.ExecuteNonQuery(sql.ToString()); } public List<T> GetList() { return DbHelper.GetList<T>($"select * from {typeof(T).Name}"); } public T GetModel(dynamic id) { var pk = GetPrimaryKey().Name; //获取主键的名称 //获取一条记录 return DbHelper.GetList<T>( $"select * from {typeof(T).Name} where {pk}=@id", new SqlParameter(pk, id)).First(); } public int Update(T model) { var tp = typeof(T); var pk = GetPrimaryKey(); //获取主键 var props = tp.GetProperties().ToList(); //获取所有的属性名称(除主键) var propNames = props.Where(p => !p.Name.Equals(pk)).Select(p => p.Name).ToList(); //update 表 set 字段1=@字段1,字段2=@字段2, where 主键名=主键值 string sql = $"update {tp.Name} set "; foreach (var propName in propNames) { sql += $"{propName}=@{propName},"; } sql = sql.Remove(sql.Length - 1); sql += $" where {pk.Name}=@{pk.Name}"; List<SqlParameter> list = new(); foreach (var prop in props) { SqlParameter parameter = new SqlParameter(prop.Name, prop.GetValue(model)); list.Add(parameter); } return DbHelper.ExecuteNonQuery(sql, list.ToArray()); } public int Delete(dynamic id) { //delete from 表名 where 主键名=@主键值 var pk = GetPrimaryKey().Name; return DbHelper.ExecuteNonQuery($"delete from {typeof(T).Name} where {pk}=@{pk}", new SqlParameter(pk,id)); } /// <summary> /// 获取主键 /// </summary> /// <returns></returns> public PropertyInfo GetPrimaryKey() { var props = typeof(T).GetProperties(); foreach (var propertyInfo in props) { //获取特性 var attrs = propertyInfo.GetCustomAttributes(typeof(KeyAttribute), false); if (attrs.Length > 0) { return propertyInfo; } } return props[0]; // 如果没有Key 特性,就让第一个属性当作主键 } }
DataTable 转 List
DataTable 转换成List
private static List<T> ToList<T>(DataTable dt) where T : class, new() { Type t = typeof(T); PropertyInfo[] propertys = t.GetProperties(); List<T> lst = new List<T>(); string typeName = string.Empty; foreach (DataRow dr in dt.Rows) { T entity = new T(); foreach (PropertyInfo pi in propertys) { typeName = pi.Name; if (dt.Columns.Contains(typeName)) { if (!pi.CanWrite) continue; object value = dr[typeName]; if (value == DBNull.Value) continue; if (pi.PropertyType == typeof(string)) { pi.SetValue(entity, value.ToString(), null); } else if (pi.PropertyType == typeof(int) || pi.PropertyType == typeof(int?)) { pi.SetValue(entity, int.Parse(value.ToString()), null); } else if (pi.PropertyType == typeof(DateTime?) || pi.PropertyType == typeof(DateTime)) { pi.SetValue(entity, DateTime.Parse(value.ToString()), null); } else if (pi.PropertyType == typeof(float)) { pi.SetValue(entity, float.Parse(value.ToString()), null); } else if (pi.PropertyType == typeof(double)) { pi.SetValue(entity, double.Parse(value.ToString()), null); } else { pi.SetValue(entity, value, null); } } } lst.Add(entity); } return lst; }
配套视频链接:
C# 高级编程,.Net6 系列 开发第三阶段,学完拿捏你的面试官,.net6 进阶学习(已完结)_哔哩哔哩_bilibili