【转载】[C#] where 关键词的用法

564 阅读4分钟

原文链接

[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的例子。