iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
0
自我挑戰組

C# 從入門到WebApi系列 第 7

[Day7] 豪想要一種能接納所有人的型別 關於多載與泛型

前言

在前一篇今晚我想來點基礎的資料結構 中我有提到關於泛型集合與boxing跟unboxing的一些概念,若是你不是很了解,希望你能花一點時間去看看~~
打到一半發現關於多載(overloading)沒有之前沒有提到
所以在本篇也會做補充

關於方法多載

我們在方法,那是什麼能吃嗎 提到建立方法時需要宣告回傳類型與傳入參數(可以為空)
如下,我宣告了一個方法
input是int型態 output是string型態

public string EatApple(int num){
    return "我吃了"+num+"個蘋果,傳入型態是int";
}

我們將input 與output 稱為signature(方法簽章)
透過不同的方法簽章
我們能夠用相同的方法名稱
執行不同的方法

舉例來說
我"再"新增一個一樣叫做EatApple的方法
input是string型態 output是string型態

public string EatApple(string input){
        return "我吃了"+input+"個蘋果,傳入型態是string";
}
void Main(){
    Console.WriteLine(EatApple(5));//我吃了5個蘋果,傳入型態是int
    Console.WriteLine(EatApple("很多"));//我吃了很多個蘋果,傳入型態是string
}

我們同樣是呼叫EatApple的方法
但是實際上呼叫的方法卻不一樣
這種用法我們稱為多載(overloading)

注意
若是我們的回傳類型不同且傳入參數相同
編譯器會報錯
我再宣告一個方法

public int EatApple(int num){
    return num;
}

當我們呼叫EatApple(5)的時候
系統會不知道你想要呼叫的是
public string EatApple(int num)
還是public int EatApple(int num)

關於泛型

我們繼續來吃蘋果
上面我們宣告了兩個方法來處理吃蘋果這件事
如果我們今天想要吃1.5顆蘋果
我們就得多新增一個方法

public string EatApple(float num){
     return "我吃了"+input+"個蘋果;
}

所以我若是想吃3000000000(超過int)上限
我們就要再寫一個for long版本的
(當然你也可以一開始就用long 不用int,但依舊得寫浮點數及字串版本)

有沒有一種型別 能抓住所有類別的心~~
有 那就是object妳以為這裡的答案是泛型嗎
正如前篇今晚我想來點基礎的資料結構
所提過的所有型別的物件都是object
所以用object就能抓住所有類型
但是一樣的我們使用object的話
就會有boxing 與 unboxing 的問題
或是動態轉換的問題
除了降低效能還容易出錯
因此就有了泛型的出現

講了那麼多 來看看實際使用吧

正如昨天所提
我們用Stack<T> 跟 Queue<T> 來使用集合泛型
這是因為我們習慣用T來表示泛型
回傳類型 方法名稱<T>(傳入參數){
//你的程式碼
}

public string EatApple<T>(T num){
    return "我吃了"+num+"個蘋果,傳入型態是"+typeof(T);
}

<T>中的T為參數傳入型態
我們能使用typeof(T)來與得T的實際型態

void Main(){
    Console.WriteLine(EatApple<int>(5));
    //我吃了5個蘋果,傳入型態是System.Int32
    Console.WriteLine(EatApple<string>("跟榴槤一樣大的一"));
    //我吃了跟榴槤一樣大的一個蘋果,傳入型態是System.String
    Console.WriteLine(EatApple<double>(1.5));
    //我吃了1.5個蘋果,傳入型態是System.Double
}

如果今天有兩個泛型類別要傳入

public int Test<T1,T2>(T1 obj1,T2 obj2){
    //你的程式碼
}

依此類推,當然T1T2只是代號 你也可以

public int Test<Doraemon,HelloKitty>(Doraemon obj1,HelloKitty obj2){
    //你的程式碼
}

如果你真的要這麼做
建議使用在他們前面加個"T" //TDoraemon
比較符合命名規範

那我今天回傳類型也要不固定捏

一樣能使用泛型

public T Example<T>(int input)
{
    //code
}

泛型約束

有些時候你會想要你的泛型方法限定傳入某些型態的類別
通常會限制只能傳入某種介面或類別
這樣只有實作介面以及繼承這些類別的才能使用
(因為還沒講到介面跟類別 所以先看看就好)
使用where來限制

public T Example<T>() where T:class
{
    //code
}

參閱泛型類型條件約束

關於反射

泛型方法經常會搭配反射作使用
因為在你實際帶型態給泛型T之前
你並不知道T是什麼類型
假設你想對Int類型的特別作處理
可以用typeof(T) 來取得泛型的type

if(typeof(T)==typeof(int))

** 那今天我有一個 Class Apple **
我想透過泛型方法來取得蘋果熟了沒

//蘋果類別
    public class Apple
    {
        public string Breed { get; set; }
        public string Origin { get; set; }
        public bool IsMature { get; set; }
    }

Main

using System;

namespace GenericFun
{
    class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple() {Breed = "Fuji", Origin = "Japan", IsMature = true};
            //宣告一顆富士蘋果,來自日本,已經成熟
            Console.WriteLine(CheckMature(apple));
        }

        public static bool CheckMature<T>(T input)
        {   
            var value = typeof(T).GetProperty("IsMature").GetValue(input);
            //取得input的IsMature欄位(取出來是object)
            if (value.GetType() == typeof(bool))//確認取出的資料是bool
            {
                return (bool) value;
            }
            return false;
        }
    }
}

我們使用typeof(T).GetProperty來取得欄位
事實上typeof(T).GetProperties能取到更多的資訊
在反射中也經常使用
我這邊只稍微帶過
有興趣可以參閱[C#.NET] 使用反射(Reflection)對物件的結構進行操作 (一)反射

事實上反射的效能極差 但是由於其便利性 所以還是經常會使用到

泛型這麼方便 我可不可以所有方法都用泛型

事實上泛型並沒有想像中的方便
由於你系統不知道你傳入的資料型別是什麼
所以該型別的方法屬性都不能使用(除非使用反射,但是反射效能差)
我們拿上方的蘋果作舉例
我們想要取得蘋果成熟沒必須大費周章
那我們今天傳入的是蘋果類別呢

public static bool CheckMature(Apple input)
{   
    return input.IsMature;
}

一行
沒了

所以如果我們今天能確立INPUT與OUTPUT型態

就盡量避免使用泛型


上一篇
[Day6] 今晚我想來點基礎的資料結構
下一篇
[Day8] 委派(delegate),Action<T> ,與Func<T,TResult>
系列文
C# 從入門到WebApi30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言