[C#]Where關鍵詞的用法

where(泛型類型約束)

where關鍵詞一個最重要的用法就是在泛型的聲明、定義中做出約束。 
約束又分爲接口約束、基類約束、構造函數約束、函數方法的約束,我們慢慢介紹。

接口約束

顧名思義,泛型參數必須實現相應的接口纔可以,看一個例子:

public interface IAccount {

        string Name {
            get;
        }

        decimal Balance {
            get;
        }
}


 public class Account : IAccount {
        private string name;
        public string Name {
            get {
                return name;
            }

        }

        private decimal balance;
        public decimal Balance {
            get {
                return balance;
            }
        }

        public Account(string name = "", decimal balance = 0) {
            this.name = name;
            this.balance = balance;
        }
   }


   public class MyClass<T> where T : IAccount {

        public MyClass() {
            Console.WriteLine("In MyClass<T> Ctor");    
        }

    }

public class MyClass<T> where T : IAccount中,where關鍵詞指定了T必須實現IAcoount的接口纔可以成功構造,例如:

namespace CSharp {
    class Program {
        static void Main(string[] args) {

            MyClass<Account> mc = new MyClass<Account>();
            //成功,Account實現了IAccount接口

            MyClass<string> m = new MyClass<string>();
            //構造失敗,string沒有實現IAccount接口,編譯器提示錯誤
        }
    }
}

T也可以是泛型接口,例如MSDN給出的例子

public class MyGenericClass<T> where T:IComparable { }  

基類約束

類型參數必須是指定的基類或派生自指定的基類,多用於繼承體系之下,看個例子:

public class Account : IAccount {
        private string name;
        public string Name {
            get {
                return name;
            }

        }

        private decimal balance;
        public decimal Balance {
            get {
                return balance;
            }
        }

        public Account(string name = "", decimal balance = 0) {
            this.name = name;
            this.balance = balance;
        }
    }


    public class AccountDrived : Account {

        public AccountDrived(string name = "", decimal balance = 0):base(name, balance) {
            Console.WriteLine("In AccountDrived Ctor");
        }

    }
   //泛型參數只能是Account或者Account的派生類
    public class MyClass2<T> where T : Account {

        public MyClass2() {
            Console.WriteLine("In MyClass2<T> Ctor");
        }

    }

    class Program {
        static void Main(string[] args) {

            MyClass2<Account> a = new MyClass2<Account>();
            MyClass2<AccountDrived> b = new MyClass2<AccountDrived>();
            //MyClass2<string> c = new MyClass2<string>(); - error
        }
    }

構造函數約束

顧名思義,對類的構造函數進行了一定的約束,舉個例子:

public class NoDefaultAccount : IAccount {
        private string name;
        public string Name {
            get {
                return name;
            }

        }

        private decimal balance;
        public decimal Balance {
            get {
                return balance;
            }
        }

        public NoDefaultAccount(string name) {
            this.name = name;
            this.balance = 0;
        }
    }


    public class Account : IAccount {
        private string name;
        public string Name {
            get {
                return name;
            }

        }

        private decimal balance;
        public decimal Balance {
            get {
                return balance;
            }
        }
        public Account(string name = "", decimal balance = 0) {
            this.name = name;
            this.balance = balance;
        }
    }


    public class AccountDrived : Account {
    }


    public class MyClass3<T> where T : class, new(){

        public MyClass3(){
            Console.WriteLine("In MyClass3<T> Ctor");
        }
    }

    class Program {
        static void Main(string[] args) {

            //1.MyClass3<Account> a = new MyClass3<Account>();
            MyClass3<AccountDrived> b = new MyClass3<AccountDrived>();//默認生成一個無參構造函數
            //2.MyClass3<NoDefaultAccount> c = new MyClass3<NoDefaultAccount>();//必須是有默認構造函數的非抽象類
        }
    }

這裏的重點是public class MyClass3<T> where T : class, new(),這表明參數T對應的類型必須是一個引用類型(class),new()表示具備無參構造函數。

NoDefaultAccount類內顯然沒有默認的構造函數,在Account中有public Account(string name = "", decimal balance = 0),給定了默認值,在AccountDerived中,由於我們沒有顯式的聲明一個構造函數,於是C#會自動生成一個AccountDerived()。

令人疑惑的是,Account是有默認構造函數的,爲何//1.MyClass3<Account> a = new MyClass3<Account>();這條語句編譯器會報錯呢? 
嘗試後發現,C#和C++不一樣,當你寫下Account a = new Account();這條語句的時候,編譯器會優先查找是否有public Account(),如果存在那麼就構造對象,否則查找public Account(value = defaultvalue)這種帶默認值的構造函數,兩者是不一樣的,並且是可以共存的。

class Account{
       //和C++不同,這並不是重定義
        public Account() {
            this.name = "xxxxx";
            this.balance = 10;
        }

        public Account(string name = "", decimal balance = 0) {
            this.name = name;
            this.balance = balance;
        }
 }

new()這種約束特指是否存在 Account()這樣的無參默認構造函數。

函數方法的約束

這種形式就比較簡單了,上述三個約束不加在泛型類中,加在函數中即可,舉個例子:

 public class Algorithm {

        public static decimal Total<TAccount>(IEnumerable<TAccount> e) 
                              where TAccount : IAccount
        //這意味着調用Total函數傳入的參數e必須是1.實現了IEnumerable接口的可迭代對象 2.e的可迭代元素必須是實現了IAcoount接口的
        {
            decimal total = 0;
            foreach(TAccount element in e) {
                total += element.Balance;
            }
            return total;
        }

        public static void Add<T>(T lhs, T rhs) where T : class, new() {
            //約束了T必須是引用類型,且必須定義了默認構造函數
            T ans = new T();
        }
    }


class Program {
        static void Main(string[] args) {

            List<Account> accounts = new List<Account>();

            accounts.Add(new Account("sixday", 100));
            accounts.Add(new Account("fiveday", 50));
            accounts.Add(new Account("sevenday", 70));

            Console.WriteLine("The answer is {0}", Algorithm.Total<Account>(accounts));

        }
    }

泛型類型約束總結
最後,做一個小總結:

where T : struct 這表明T必須是一個值類型,像是int,decimal這樣的
where T : class 這表明T必須是一個引用類型,像是自定義的類、接口、委託等
where T : new() 這表明T必須有無參構造函數,且如果有多個where約束,new()放在最後面
where T : [base class name] 這表明T必須是base class類獲其派生類
where T : [interface name] 這表明T必須實現了相應的接口
更多例子可以參考MSDN

where (查詢表達式)
除了用於泛型約束之外,where還常用於查詢表達式,可以直接參考MSDN的例子。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章