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
海闊平魚躍,天高任我行,給我一片藍天,讓我自由翱翔。