C# 基礎知識系列- 5 反射和泛型

来源:https://www.cnblogs.com/c7jie/archive/2020/03/31/12609508.html
-Advertisement-
Play Games

前言 為什麼要把反射和泛型放在一起講呢,這裡是處於個人對C 的一個很棒的觀感,因為C 的反射是可以獲取泛型里的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除類型信息。 那麼問題來了,什麼是泛型,什麼又是反射呢? 泛型 請原諒我先介紹泛型,因為沒有泛型基礎直接介紹反 ...


前言

為什麼要把反射和泛型放在一起講呢,這裡是處於個人對C#的一個很棒的觀感,因為C#的反射是可以獲取泛型里的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除類型信息。
那麼問題來了,什麼是泛型,什麼又是反射呢?

泛型

請原諒我先介紹泛型,因為沒有泛型基礎直接介紹反射是不完整的,就比如說你辛辛苦苦拿到一個類的反射信息,等用的時候才發現結果這是一個泛型類,那還得解析這個類的泛型的信息,這時候就必須先有一個泛型的基礎。
那麼什麼是泛型呢,先看看百度百科給的定義:

泛型是程式設計語言的一種特性。允許程式員在強類型程式設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須作出指明。各種程式設計語言和其編譯器、運行環境對泛型的支持均不一樣。將類型參數化以達到代碼復用提高軟體開發工作效率的一種數據類型。泛型類是引用類型,是堆對象,主要是引入了類型參數這個概念。

額,說實話哈,有一部分我沒看懂他寫的是啥。根據我的理解,泛型就是模板類里套的參數。就好比我們從網上找到一個好看的PPT模板,我們在寫PPT的時候根據我們的主題套用這個模板,然後寫出一個很好看的PPT,被老闆表揚升職加薪。嗯,事實上用好了泛型也會升職加薪。

泛型說的籠統一些就是類型參數化的過程,我們之前介紹的List就是一個泛型類。泛型分泛型類/介面和泛型方法。泛型類和泛型介面可以看做是一種,因為它的泛型參數是用在整個結構體裡面的(註意不是結構,struct);泛型方法又有參數泛型和返回值泛型兩種。

聲明一個泛型類/介面

public class Template<T>
{
	private T data;
	public void SetTemplate(T temp)
    {
    	data = temp;
    }
    public T GetTemplate()
    {
    	return data;
    }
}

上述示例是一個簡單的泛型類,體現了泛型類的特點。在聲明類的時候,聲明一個泛型占位符T ,在下麵的屬性、欄位、方法的參數和方法的返回值都可以使用這個占位符,約定類型一致。

泛型的介面和泛型類是一致的,只不過介面沒有方法的實現內容也就是方法體而已。

泛型類的使用

// 繼續上面的代碼
Template<int> temp = new Template<int>();
temp.SetTemplate(10);
int ten = temp.GetTemplate();

使用泛型類和普通類不同的地方就是,泛型類告訴編譯器你要傳遞的類型。使用<> 做標記,中間寫類型,表示這是一個泛型為XXX的泛型類。通常與其他語言不同的地方是,C#的泛型支持所有類型,意思就是在沒有額外聲明的時候,可以使用任意類型作為泛型參數傳遞。

泛型方法

C#也可以聲明一個方法為泛型方法,方法的泛型聲明是聲明在方法名的後面,參數列表的前方。

public void TemplateMethod<T>(T arg);
public T TemplateMethod1<T>();
public T TemplateMethod2<T>(T arg);

上述三個都是合規的泛型方法聲明。泛型可以是參數,也可以是返回值,還能既是返回值又是參數。

那麼問題來了,多個泛型參數該怎麼聲明?

如下:

public T2 TemplateMothod3<T1,T2>(T1 arg);
public T3 TemplateMothod4<T1,T2,T3>(T1 arg,T2 arg2);

在兩個尖括弧中間放入多個泛型,然後用逗號隔開,與參數列表和返回值的類型一一對應。

泛型方法的使用

TemplateMethod(10);// 方式 1
int it = TemplateMethod1<int>();// 方式 2

由於篇幅和時間的關係(主要是我寫這篇的時候時間有點晚了。。)就不對之前所有的方法進行演示了。

這裡簡單介紹一下泛型方法的使用:

  • 方式1 隱藏了一個泛型參數,這是因為如果泛型是參數的話,c#會根據參數的類型自動解析對應的泛型類型是什麼,方式1 等同於TemplateMethod<int>(10);
  • 方式2 當泛型參數是返回值時,必須告知具體的泛型類型。

泛型約束和泛型標記

約束

在實際開發過程中,我們會對一些泛型類的泛型參數進行類型約束,那麼泛型約束應該怎麼寫呢,看示例:

public void Demo<T>(T arg) where T : 約束內容
public void Demo<T,P>(T arg,P arg1) where T: 約束內容 where P:約束內容

如果對多個參數進行約束,就寫多個where。

泛型的約束有一下幾種:

  • class 表示這是個引用類型
  • new() 表示必須有一個無參構造函數
  • struct 表示是個結構體
  • 具體的類名或介面名 表示這個參數必須是這個類的子類或介面的實現類

泛型標記

在C#里有個很有意思的地方,那就是泛型標記。

泛型支持 in/out作為占位符T的前置標記。那這兩個標記是什麼意義呢,in表示這個類型參數只能作為參數列表的類型進行傳遞,out表示這是一個返回值的類型,示例如下:

public T2 Demo<in T1,out T2>(T1 t1);

類和方法的標記大同小易,基本上是一致的。

反射

反射在很多地方都有著使用,這裡先簡單的介紹一下C#中的反射相關內容,因為細講的話會涉及到很多東西而且還需要很多前置概念,不過在自己寫框架之前不需要涉及到太多反射的內容。

反射,英文名 reflect,簡單的介紹就是將類型對象化,然後操作這個對象的技術。

我們先創建一個示例類:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Person()
    {
        Name = "小李";
        Age = 24;
    }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public string SayHi()
    {
        return "你好,我叫" + Name + "我的年紀是 " + Age;
    }
}

獲取一個類型對象

首先需要註意的一個類:Type,這個類是反射技術里的基石,甚至可以說是核心,表示一個類的類型信息。

那麼,我們該如何獲取類型對象呢?在C#中常見的有如下兩個方法:

  1. 使用typeof 關鍵字
Type personType = typeof(Person);
  1. 通過對象,使用GetType 方法
Person person = new Person();
Type personType = person.GetType();

如果我們在編寫程式的時候,知道要獲取什麼類的Type對象的話,建議使用typeof獲取。如果我們只有一個對象,需要通過這個對象進行操作的話,那麼最好使用GetType來獲取。

現在我們獲取到了一個Person類的Type對象,可以用來做什麼呢?

Type對象的用處

  1. 獲取類名:personType.Name
  2. 獲取所有屬性:personType.GetProperties()
  3. 獲取所有方法:personType.GetMethods()
  4. 獲取所有構造函數:personType.GetConstructors()

現在我們一一介紹一下這四種寫法:

第一條:顧名思義,獲取到的結果是Person 這個值。

第二條:該方法會返回一個類型為PropertyInfo[] 的數組,這個數組裡包含著所有使用public聲明的屬性。當然也可以通過指定的屬性名獲取屬性對象:personType.GetProperty("Name") 這裡會獲取到Person類的Name屬性。

第三條: 獲取該類所有public的方法,並將其封裝成一組類型是MethodInfo的對象數組。同理,也可以根據方法名進行檢索:personType.GetMethod("SayHi") ,就能獲取對應的SayHi方法。不過,如果有同名方法的話,就可能會出現獲取到的方法不是你想要的了。嗯,這部分會放到精講反射的時候再來細說。

第四條: 獲取構造函數,返回的是一個類型是ConstructorInfo的數組,表示所有的構造方法,不過可惜的是,沒有根據名字檢索的方法了,因為構造方法就一個名。

使用PropertyInfo動態操作一個對象的屬性值

我們通過上一小節獲取到了一個類的屬性PropertyInfo,現在可以利用這個屬性進行後續的操作:

Person person = new Person();
Type personType = person.GetType();
PropertyInfo prop = personType.GetProperty("Name");//獲取Name屬性
Object value = prop.GetValue(person);// 獲取 對象 person 的Name屬性值
prop.SetValue(person, "wangyipeng");// 為對象 person的Name屬性設置值為 wangyipeng

需要註意的是:

如果 類的屬性只有get,那麼在調用SetValue時會報錯。可能要問了,我們知道是有set,但是程式怎麼判斷呢?通過prop.CanWrite 的值進行判斷,如果值是true則表明這個屬性可以寫入值,否則不能。

同理,可以很輕易的聯想到如果只有set,那麼GetValue也會報錯,與之相對應的就是prop.CanRead屬性了。

使用MethodInfo手動執行一個對象的方法

首先,獲得到一個對象里的某一個方法:

Person person = new Person();
Type personType = person.GetType();
MethodInfo method = personType.GetMethod("SayHi");

現在獲取到了 方法對象,該怎麼執行呢?

MethodInfo有一個Invoke方法,這個方法有兩個重載版本。其中有一個是:Invoke(object obj, object[] parameters),第一個參數是要執行的方法所屬的對象,後面的數組參數是對應方法的參數列表,如果為空則填null即可。該方法有個返回值,類型是object,如果方法是沒有返回值的方法,那麼Invoke的返回值就是null。

通過反射獲取一個對象

通過反射獲取一個類的類型對象有幾種方式,先介紹一個不用類型的方式:

Person p = Activator.CreateInstance<Person>();

這種方式有一個要求,Person必須有一個無參的構造函數。

第二種方式:

Type personType = typeof(Person);
object p = Activator.CreateInstance(personType);//使用無參構造函數
p = Activator.CreateInstance(personType, "小王", 19);//使用Person(string,int)這個構造函數

當需要傳遞參數的時候,參數類型必須與對應的構造函數一一對應,如果順序變了,可能會出現找不到對應類的問題。

第三種:

//types 是參數列表的參數類型集合,順序與實際參數順序一致
ConstructorInfo cons = personType.GetConstructor(Type[] types);
/*
實際上應該是這個調用方
ConstructorInfo cos = personType.GetConstructor(new[]{ typeof(string), typeof(int)});
*/
object person  = cos.Invoke(new object[] {"王先生", 19});

這時候一個簡單的反射介紹就到這裡了,反射這裡還有一大篇的內容要將。這部分我會放到基礎篇完結之後再做一個統一介紹的。不過先道個歉,沒介紹泛型在反射的應用。

註:代碼里映射的王先生是我一個故人,最近與他有一些糾紛。
更多內容煩請關註我的博客

file


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 聽說隔壁用 Lombok 的六點就下班了,我也想六點下班! 好的,那麼這篇文章就介紹下 什麼是 Lombok , Lombok 做了什麼 以及 Lombok 是怎麼做的 ? 在介紹之前,先通過是否使用 Lombok 的效果來看下對比,首先來看下沒有 Lombok 之前,我們的一個簡單的 Java 對 ...
  • 在使用scrapy抓取網頁時, 如果遇到使用js動態渲染的頁面, 將無法提取到在瀏覽器中看到的內容. 針對這個問題scrapy官方給出的方案是scrapy selenium, 這是一個把selenium集成到scrapy的開源項目, 它使用selenium抓取已經渲染好(js代碼已經執行完成)的動態 ...
  • 調用三方介面返回值JSON字元串帶BOM頭"\ufeff",JSON解析死活報錯。 我是用SpringBoot的 調用三方介面的,一開始返回值我是用對象接收返回值,發現一直報錯,我以為是 的接收轉換有問題,就將返回值換成了 類型去接收。接收到字元串後再轉JSON、JSON字元串解析死活報錯。 介面返 ...
  • class Res { private String name; private int count = 1; private boolean flag; public synchronized void set(String name) { while (flag) { try { this.wa ...
  • title: Java基礎語法(7) 數組 blog: "CSDN" data: "Java學習路線及視頻" 1.數組的概述 數組(Array),是多個相同類型數據按一定順序排列的集合,並使用一個名字命名,並通過編號的方式對這些數據進行統一管理。 數組的常見概念 數組名 下標(或索引) 元素 數組的 ...
  • title: Java基礎語法(6) 註釋 blog: "CSDN" data: "Java學習路線及視頻" 用於註解說明解釋程式的文字就是註釋。 提高了代碼的閱讀性;調試程式的重要方法。 註釋是一個程式員必須要具有的良好編程習慣。 將自己的思想通過註釋先整理出來,再用代碼去體現 1.單行註釋 格式 ...
  • 一. 獲取多個單元格的值報錯:AttributeError: 'tuple' object has no attribute 'value' 需要讀取的sample.xlsx 代碼讀取的是A3:B10之間的單元格 運行結果: 二. 如何解決 上面報錯信息是,元組對象沒有屬性"value",我們先來看 ...
  • 原創聲明 本文作者:黃小斜 轉載請務必在文章開頭註明出處和作者。 什麼是消息隊列 “RabbitMQ?”“Kafka?”“RocketMQ?”...在日常學習與開發過程中,我們常常聽到消息隊列這個關鍵詞,可能你是熟練使用消息隊列的老手,又或者你是不懂消息隊列的新手,不論你了不瞭解消息隊列,本文都將帶 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...