1. 程式人生 > >Lambda表示式(2)

Lambda表示式(2)

Lambda表示式轉換

和匿名方法表示式類似,Lambda表示式可以歸類為一種擁有特定轉換規則的值。這種值沒有型別,但可以被隱式地轉換為一個相容的委託型別。特別地,當滿足下列條件時,委託型別D兼容於Lambda表示式L

lDL具有相同數量的引數;

l如果L具有顯式型別的引數列表,D中每個引數的型別和修飾符必須和L中相應的引數完全一致;

l如果L具有隱式型別的引數列表,則D中不能有refout引數;

l如果D具有void返回值型別,並且L的表示式體是一個表示式,若L的每個引數的型別與D的引數一致,則L的表示式體必須是一個可接受為statement-expression的有效表示式;

l如果

D具有void返回值型別,並且L的表示式體是一個語句塊,若L的每個引數的型別與D的引數一致,則L的表示式體必須是一個有效語句塊,並且該語句塊中不能有帶有表示式的return語句;

l如果D的返回值型別不是void,並且L的表示式體是一個表示式,若L的每個引數的型別與D的引數一致,則L的表示式體必須是一個可以隱式轉換為D的返回值型別的有效表示式;

l如果D的返回值型別不是void,並且L的表示式體是一個語句塊,若L的每個引數的型別與D的引數一致,則L的表示式體必須是一個有效的語句塊,該語句塊不能有可達的終點(即必須有return語句),並且每個return語句中的表示式都必須能夠隱式轉換為D的返回值型別。

後面的例子將使用一個範型委託F<U, T>表示一個函式,它具有一個型別為U的引數u,返回值型別為T

delegate T F<U, T>(U u);

我們可以像在下面這樣賦值:

F<int, int> f1 = x => x + 1;           

F<intdouble> f2 = x => x + 1;

每個Lambda表示式的引數和返回值型別通過將Lambda表示式賦給的變數的型別來檢測。第一個賦值將Lambda表示式成功地轉換為了委託型別Func<int, int>,因為x的型別是intx + 1

是一個有效的表示式,並且可以被隱式地轉換為int。同樣,第二個賦值成功地將Lambda表示式轉換為了委託型別Func<int, double>,因為x + 1的結果(型別為int)可以被隱式地轉換為double型別。

來看如下程式碼,如果這樣賦值會怎麼樣?

F<double, int> f3 = x => x + 1;

我們執行上面的程式碼,編譯器會報如下兩條錯誤:

1)無法將型別“double”隱式轉換為“int”。存在一個顯式轉換(是否缺少強制轉換?)。

2)無法將Lambda表示式轉換為委託型別“F<doubleint>”,原因是塊中的某些返回型別不能隱式轉換為委託返回型別。

其實產生一個編譯期錯誤原因是,x給定的型別是doublex + 1的結果(型別為double)不能被隱式地轉換為int

型別推斷

當在沒有指定型別引數的情況下呼叫一個範型方法時,一個型別推斷過程會去嘗試為該呼叫推斷型別引數。被作為引數傳遞給範型方法的Lambda表示式也會參與這個型別推斷過程。

最先發生的型別推斷獨立於所有引數。在這個初始階段,不會從作為引數的Lambda表示式推斷出任何東西。然而,在初始階段之後,將通過一個迭代過程從Lambda表示式進行推斷。特別地,當下列條件之一為真時將會完成推斷:

l引數是一個Lambda表示式,以後簡稱為L,從其中未得到任何推斷;

l相應引數的型別,以後簡稱為P,是一個委託型別,其返回值型別包括了一個或多個方法型別引數;

lPL具有相同數量的引數,P中每個引數的修飾符與L中相應的引數一致,或者如果L具有隱式型別的引數列表時,沒有引數修飾符;

lP的引數型別不包含方法型別引數,或僅包含於已經推斷出來的型別引數相相容的一組型別引數;

l如果L具有顯式型別的引數列表,當推斷出來的型別被P中的方法型別引數取代了時,P中的每個引數應該具有和L中相應引數一致的型別。

l如果L具有隱式型別的引數列表,當推斷出來的型別被P中的方法型別引數取代了並且作為結果的引數型別賦給了L時,L的表示式體必須是一個有效的表示式或語句塊。

l可以為L推斷一個返回值型別。

對於每一個這樣的引數,都是通過關聯P的返回值型別和從L推斷出的返回值型別來從其上進行推斷的,並且新的推斷將被新增到累積的推斷集合中。這個過程一直重複,直到無法進行更多的推斷為止。

在型別推斷和過載抉擇中,Lambda表示式L的“推斷出來的返回值型別”通過以下步驟進行檢測:

l如果L的表示式體是一個表示式,則該表示式的型別就是L的推斷出來的返回值型別。

l如果L的表示式體是一個語句塊,若由該塊中的return語句中的表示式的型別形成的集合中恰好包含一個型別,使得該集合中的每個型別都能隱式地轉換為該型別,並且該型別不是一個空型別,則該型別即是L的推斷出來的返回值型別。

l除此之外,無法從L推斷出一個返回值型別。

作為包含了Lambda表示式的型別推斷的例子,請考慮System.Query.Sequence類中宣告的Select擴充套件方法:

namespace System.Query

    {

publicstaticclassSequence

        {

publicstaticIEnumerable<S> Select<T, S>(

thisIEnumerable<T> source,

Func<T, S> selector)

            {

foreach (T element in source) yieldreturn selector(element);

            }

        }

    }

假設使用using語句匯入了System.Query名稱空間,並且定義了一個Customer類,具有一個型別為string的屬性NameSelect方法可以用於從一個Customer列表中選擇名字:

List<Customer> customers = GetCustomerList();

IEnumerable<string> names = customers.Select(c => c.Name);

對擴充套件方法Select的呼叫將被處理為一個靜態方法呼叫:

IEnumerable<string> names = Sequence.Select(customers, c => c.Name);

由於沒有顯式地指定型別引數,將通過型別推斷來推導型別引數。首先,customers引數被關聯到source引數,T被推斷為Customer。然後運用上面提到的拉姆達表示式型別推斷過程,C的型別是Customer,表示式c.Name將被關聯到selector引數的返回值型別,因此推斷Sstring。因此,這個呼叫等價於:

Sequence.Select<Customer, string>(customers, (Customer c) => c.Name);

並且其返回值型別為IEnumerable<string>

下面的例子演示了Lambda表示式的型別推斷是如何允許型別資訊在一個範型方法呼叫的引數之間“流動”的。對於給定的方法:

static Z F<X, Y, Z>(X value, Func<X, Y> f1, Func<Y, Z> f2) { return f2(f1(value)); }

現在我們來寫這樣一個呼叫,來看看它的推斷過程:

double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => TotalSeconds);

型別推斷過程是這樣的:首先,引數"1:15:30"被關聯到value引數,推斷Xstring。然後,第一個Lambda表示式的引數s具有推斷出來的型別string,表示式TimeSpan.Parses)被關聯到f1的返回值型別,推斷YSystem.TimeSpan。最後,第二個Lambda表示式的引數t具有推斷出來的型別System.TimeSpan,並且表示式t.TotalSeconds被關聯到f2的返回值型別,推斷Zdouble。因此這個呼叫的結果型別是double

過載抉擇

引數列表中的Lambda表示式將影響到特定情形下的過載抉擇(也稱過載分析,過載解析等,即從幾個過載方法中選擇最合適的方法進行呼叫的過程)。

下面是新新增的規則:

對於Lambda表示式L,且其具有推斷出來的返回值型別,當委託型別D1和委託型別D2具有完全相同的引數列表,並且將L的推斷出來的返回值型別隱式轉換為D1的返回值型別要優於將L的推斷出來的返回值型別隱式轉換為D2的返回值型別時,稱LD1的隱式轉換優於LD2的隱式轉換。如果這些條件都不為真,則兩個轉換都不是最優的。

表示式樹

表示式樹允許將Lambda表示式表現為資料結構而不是可執行程式碼。一個可以轉換為委託型別DLambda表示式,也可以轉換為一個型別為System.Linq.Expressions. Expression<D>的表示式樹。將一個Lambda表示式轉換為委託型別導致可執行程式碼被委託所生成和引用,而將其轉換為一個表示式樹型別將導致建立了表示式樹例項的程式碼被髮出。表示式樹是Lambda表示式的一種高效的記憶體中資料表現形式,並且使得表示式的結構變得透明和明顯。

如下面的例子將一個Lambda表示式分別表現為了可執行程式碼和表示式樹。由於存在到Func<int, int>的轉換,因此存在到Expression<Func<int, int>>的轉換。程式碼如下所示:

 using System.Linq.Expressions

// 程式碼

Func<intint> f = x => x + 1;

 // 資料

 Expression<Func<intint>> e = x => x + 1;

在這些賦值完成之後,委託f標識一個返回x + 1的方法,而表示式樹e表示一個描述了表示式x + 1的資料結構。