第四單元 泛型

来源:https://www.cnblogs.com/xuyubing/archive/2023/06/13/17476723.html
-Advertisement-
Play Games

1. 什麼是泛型 編寫一個方法,實現兩數相加並返回結果。 作用 泛型增強了代碼的可讀性 泛型有助於實現代碼的重用、保護類型的安全以及提高性能。 我們可以創建泛型集合類。 泛型實現了類型和方法的參數化 我們還可以對泛型類進行約束以訪問特定數據類型的方法。 關於泛型數據類型中使用的類型的信息可在運行時通 ...


1. 什麼是泛型

編寫一個方法,實現兩數相加並返回結果。

 

作用

  1. 泛型增強了代碼的可讀性

  2. 泛型有助於實現代碼的重用、保護類型的安全以及提高性能。

  3. 我們可以創建泛型集合類。

  4. 泛型實現了類型和方法的參數化

  5. 我們還可以對泛型類進行約束以訪問特定數據類型的方法。

  6. 關於泛型數據類型中使用的類型的信息可在運行時通過使用反射獲取。

 

定義

泛型是可以當作任意一種且由編譯期間決定其最終類型的數據類型。通俗來講,泛型,即泛指某種類型。

 

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以來,支持了泛型介面的協變和逆變。

 

泛型協變

如果子類泛型隱式轉換成基類泛型,使用泛型協變

 

  1. 先準備好兩個子父類

    public class Animal
    {
        public virtual void Run()
        {
            Console.WriteLine("動物在跑");
        }
    }
    
    public class Dog:Animal
    {
        public override void Run()
        {
            Console.WriteLine("狗在跑");
        }
    }

     

  2. 定義好泛型協變介面,

    public interface IFactory<out T> // out 協變 只能應用於interface
    {
        T Create(); 
    }
    
    
  3. 實現協變介面
public class FactoryImpl<T>:IFactory<T> where T:new()
{
    public T Create()
    {
        return new T();
    }
}

 

 

 

  1. 測試泛型協變

    [Test]
    public void Test3()
    {
        IFactory<Dog> iFactory = new FactoryImpl<Dog>();
        IFactory<Animal> parentFactory = iFactory; // 協變
    
        Animal animal = parentFactory.Create();
        animal.Run();// 輸出結果:狗在跑
    }
  • 泛型介面中的out關鍵字必不可少

  • out 協變 只能應用於interface

 

泛型逆變

如果基類泛型隱式轉換成子類泛型,使用泛型逆變。

 

  1. 關於通知的一個介面

    public interface INotification
    {
        public string Message { get; }
    }
    
    // 關於通知介面的抽象實現。
    public abstract class Notification : INotification
    {
        public abstract string Message { get; }
    }
  2. 關於通知抽象類的具體實現。

    public class MainNotification : Notification
    {
        public override string Message => "您有一封新的郵件";
    }
  3. 接下來,需要把通知的信息發佈出去,需要一個發佈通知的介面INotifier,該介面依賴INotification,大致INotifier<INotification>,而最終顯示通知,我們希望INotifier<MailNotification>,INotifier<INotification>轉換成INotifier<MailNotification>,這是逆變,需要關鍵字in。

    public interface INotifier<in T> where T : INotification
    {
        void Notifier(T notification);
    }

     

  4. 實現INotifier:

    public class Notifier<T> : INotifier<T> where T : INotification
    {
        public void Notify(T notification)
        {
            Console.WriteLine(notification.Message);
        }
    }

     

  5. 客戶端調用

    [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 框架,對象關係映射。

  1. 從 老師提供utils 文件夾中將DbHelper拷貝至當前你的項目中

  2. nuget引用:1:Microsoft.Extensions.Configuration,2:System.Data.SqlClient

  3. 封裝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

 

 

 

海闊平魚躍,天高任我行,給我一片藍天,讓我自由翱翔。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #搭建springboot環境(idea環境) 實現步驟: 1.基礎環境配置 2.maven配置 3.編寫第一個程式helloworld(可能有兩個小問題) 4.運行(jar包運行,命令行運行) 一.基礎環境配置 進入idea,點擊file->new->project,在彈出的頁面上,選擇sprin ...
  • Spring的Bean生命周期包括以下步驟: 1、實例化(Instantiation):當Spring容器接收到創建Bean的請求時,它會先實例化Bean對象。這個過程可以通過構造函數、工廠方法或者反序列化等方式完成; 2、屬性賦值(Populate Properties):在實例化Bean對象後, ...
  • ## 教程簡介 Axure RP是一款專業的快速原型設計工具。Axure(發音:Ack-sure),代表美國Axure公司;RP則是Rapid Prototyping(快速原型)的縮寫。 Axure RP是美國Axure Software Solution公司旗艦產品,是一個專業的快速原型設計工具, ...
  • ## 教程簡介 AWS Lambda 是AWS在2014年推出的「無伺服器」(Serverless)計算服務,用戶無需管理伺服器,可以更專註自己業務。由於上手簡單,而且真正利用了雲的優勢,Lambda快速成為了一項明星服務。 Lambda 在可用性高的計算基礎設施上運行您的代碼,執行計算資源的所有管 ...
  • 經過前幾篇文章的講解,初步瞭解ASP.NET Core MVC項目創建,啟動運行,以及命名約定,創建控制器,視圖,模型,接收參數,傳遞數據ViewData,ViewBag,路由,頁面佈局,wwwroot和客戶端庫,Razor語法,EnityFrameworkCore與資料庫,HttpContext,... ...
  • ## 一:背景 ### 1. 講故事 前段時間訓練營里有朋友問 `記憶體映射文件` 是怎麼玩的?說實話這東西理論我相信很多朋友都知道,就是將文件映射到進程的虛擬地址,說起來很容易,那如何讓大家眼見為實呢?可能會難倒很多人,所以這篇我以自己的認知嘗試讓大家眼見為實。 ## 二:如何眼見為實 ### 1. ...
  • 本篇為學習李應保老師所著的《WPF專業編程指南》並搭配`WPF`開發聖經《WPF編程寶典第4版》以及痕跡大佬《WPF入門基礎教程系列》文章所作筆記,對應《WPF專業編程指南》第 9-10 章之間內容,主要概述`WPF`中關於樣式及模板部分的梳理及示例應用,希望可以幫到大家
  • 前一段時間有網友問Excel轉pdf怎麼轉pdf,小編幫他實現了一個,方法是使用EPPlus和PdfSharp組件實現,由於依賴OfficeOpenXml他也沒有用上,後來小編又實現了二種不依賴OfficeOpenXml的方法。本文將介紹這三種方法實現Excel轉pdf。 **一、EPPlus和Pd ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...