程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 編寫高質量代碼改善C#程序的157個建議[協變和逆變]

編寫高質量代碼改善C#程序的157個建議[協變和逆變]

編輯:C#入門知識

前言

本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要學習記錄以下內容:

  建議42、使用泛型參數兼容泛型接口的不可變性

  建議43、讓接口中的泛型參數支持協變

  建議44、理解委托中的協變

  建議45、為泛型類型參數指定協變

建議42、使用泛型參數兼容泛型接口的不可變性

讓返回值類型返回比聲明的類型派生程度更大的類型,就是“協變”。協變不是一種新出現的技術,在以往的編碼中,我們已經在不自覺地使用協變。以下的代碼就是一個不自覺應用協變的例子:

    class Employee
    {
        public string Name { get; set; }
    }

    class Programmer : Employee
    { 
        
    }
    class Program
    {
        public Employee GetAEmployee(string name)
        {
            Console.WriteLine("我是雇員:"+name);
            return new Programmer() { Name = name };
        }
        static void Main(string[] args)
        {

        }
    }

Programmer是Employee的子類,所以一個Programmer對象也就是一個Employee對象。方法GetAEmployee返回一個Programmer的對象,也就是相當於返回了一個Employee對象。正是因為在FCL4.0以前的版本中,協變是如此自然的一種應用,所以我們很有可能寫出這樣的代碼:

    class Employee
    {
        public string Name { get; set; }
    }

    class Programmer : Employee
    { 
        
    }

    interface ISalary<T>
    {
        void Pay();
    }

    class BaseSalaryCounter<T> : ISalary<T>
    {

        public void Pay()
        {
            Console.WriteLine("Pay base salary");
        }
    }

接下來看一下Main函數中的調用:

    class Program
    {
        static void Main(string[] args)
        {
            ISalary<Programmer> s = new BaseSalaryCounter<Programmer>();
            PrintSalary(s);
        }

        static void PrintSalary(ISalary<Employee> s)
        {
            s.Pay();
        }
    }

咋眼一看,問題應該不大,不過編譯器卻出現了錯誤。

編譯器對於接口和委托類型參數的檢查是非常嚴格的,除非用關鍵字out特別聲明(這個會在下一個建議中進行闡述),不然這段代碼只會編譯失敗,要讓PrintSalary完成需求,我們可以使用泛型類型參數:

        static void Main(string[] args)
        {
            ISalary<Programmer> s = new BaseSalaryCounter<Programmer>();
            PrintSalary(s);
        }

        static void PrintSalary<T>(ISalary<T> s)
        {
            s.Pay();
        }

可能有人會注意到,本建議開頭處指出“協變”是針對返回值而言的,但是所舉的這個例子卻並沒有體現“返回值”這個概念。實際上,只要泛型類型參數在一個接口聲明中不被用來作為方法的輸入參數,我們都可姑且把它堪稱是“返回值”類型的。所以,本建議中這種模式是滿足“協變”的定義的。但是,只要將T作為輸入參數,便不滿足“協變”的定義了。

 

建議43、讓接口中的泛型參數支持協變

 除了建議42中提到的使用泛型參數兼容泛型接口的不可變性外,還有一種辦法就是為接口中的泛型聲明加上out關鍵字來支持協變,如下所示:

namespace ConsoleApplication10
{
    class Employee
    {
        public string Name { get; set; }
    }

    class Programmer : Employee
    { 
        
    }

    interface ISalary< out T>
    {
        void Pay();
    }

    class BaseSalaryCounter<T> : ISalary<T>
    {

        public void Pay()
        {
            Console.WriteLine("Pay base salary");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            ISalary<Programmer> s = new BaseSalaryCounter<Programmer>();
            PrintSalary(s);
        }

        static void PrintSalary(ISalary<Employee> s)
        {
            s.Pay();
        }
    }
}

out關鍵字是FCL4.0中新增的功能,它可以在泛型接口和委托中使用,用來讓類型參數支持協變性。通過協變,可以使用比聲明的參數派生類型更大的參數。通過以上的例子我們應該能理解這種應用。

FCL4.0對多個接口進行了修改以支持協變,如IEnumerable<out T>、IEnumerator<out T>、IQueryable<out T>等。由於IEnumerable<out T>現在支持協變,所以上段代碼在FCL4.0中能運行得很好。

在我們自己的代碼中,如果要編寫泛型接口,除非確定該接口中的泛型參數不涉及變體,否則都建議加上out關鍵字。協變增大了接口的使用范圍,而且幾乎不會帶來什麼副作用。

建議44、理解委托中的協變

委托中的泛型變量天然是部分支持協變的。為什麼說是“部分支持協變”呢?來看一下下面的例子:

    class Employee
    {
        public string Name { get; set; }
    }

    class Manager:Employee
    {

    }

    class Program
    {
        public delegate T GetEmployeeHandler<T>(string name);
        static void Main(string[] args)
        {
            GetEmployeeHandler<Employee> getAEmployee = GetAManager;
            Employee e = getAEmployee("Mike");
            Console.ReadLine();
        }

        static Manager GetAManager(string name)
        {
            Console.WriteLine("我是經理:" + name);
            return new Manager() { Name = name };
        }

        static Employee GetAEmployee(string name)
        {
            Console.WriteLine("我是雇員:"+name);
            return new Employee() {Name=name };
        }
    }

上文中,方法GetAManager返回的是一個Manager,但是在使用中,其實是將其賦值給了一個泛型參數為Employee的委托變量。我們也許會認為委托中的泛型變量不再需要out關鍵字,這是錯誤的理解。因為存在下面這樣一種情況,所以編譯報錯:

要讓上面的代碼編譯通過,同樣需要為委托中的泛型參數指定out關鍵字:

public delegate T GetEmployeeHandler< out T>(string name);

除非考慮到該委托聲明肯定不會用於可變性,否則,為委托中的泛型參數指定out關鍵字將會拓展該委托的應用,建議在實際的編碼工作中永遠這樣使用,實際上,FCL4.0中的一些委托聲明已經用out關鍵字來讓委托支持協變了,如我們常常會使用到的:

public delegate TResult Func<out TResult>();

建議45、為泛型類型參數指定協變

 逆變是指方法的參數可以是委托或泛型接口的參數類型的基類。FCL4.0中支持逆變的常用委托有:

Func<in T,out TResult>

Predicate<in T>

常用泛型接口有:

ICompare<in T>

下面的例子演示了泛型類型參數指定逆變所帶來的好處:

    public interface IMyComparable<in T>
    {
        int Compare(T other);
    }

    public class Employee:IMyComparable<Employee>
    {
        public string Name { get; set; }

        public int Compare(Employee other)
        {
            return Name.CompareTo(other.Name);
        }
    }

    public class Programmer:Employee,IMyComparable<Programmer>
    {
        public int Compare(Programmer other)
        {
            return Name.CompareTo(other.Name);
        }
    }

    public class Manager : Employee
    { 
    
    }

    class Program
    {
        static void Main(string[] args)
        {
            Programmer p = new Programmer() { Name = "Mike" };
            Manager m = new Manager() { Name = "aehyok" };
            Test(p, m);
        }

        static void Test<T>(IMyComparable<T> t1, T t2)
        { 
            //省略
        }
    }

在上面的這個例子中,如果不為接口IMyComparable的泛型參數T指定in關鍵字,將會導致Test(p,m)編譯錯誤。由於引入了接口的逆變性,這讓方法Test支持了更多的應用場景。在FCL4.0之後版本的實際編碼中應該始終注意這一點。

 

英語小貼士

1、Then, please give me a new reservation.——那麼,請幫我重新訂位。

2、Sorry, this flight is full.——抱歉,這班飛機已客滿。

3、What is the possibility of my gettinga seat if I wait?——若是我在此等候,有機位的機率有多大?

4、When will the next flight to Los Angeles leave?——下一班飛往洛杉機的班機何時起飛?

5、The day after tomorrow, Friday.——後天,星期五。

6、That will be fine. What's the flight number and departure time?——太好了。請告訴我班機號碼與起飛時間?

7、What is the fare?——費用多少?

作者:aehyok

出處:http://www.cnblogs.com/aehyok/

感謝您的閱讀,如果您對我的博客所講述的內容有興趣,那不妨點個推薦吧,謝謝支持:-O。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved