標籤:

C#7新增的模式匹配為什麼不是表達式?

感覺現在這個模式匹配語法太糟糕了。首先switch...case語法就很啰嗦,現在已經很少使用。不過最重要的問題在於switch...case是語句,這樣一來模式匹配的表達能力就要大打折扣,甚至顯得非常的雞肋,我甚至都不太想用這麼糟糕的語法來寫個什麼東西。

舉個栗子:

C# 7.0的目前的模式匹配語法是這樣的:

var result = match( input );

int? match( object input )
{
switch( input )
{
case null:
return null;
case int number:
if( number &> 0 )
return 1;
else
return 0;
default:
return -1;
}
}

代碼很簡單,如果輸入是null,則取null,如果輸入是數字且大於0,取1,輸入是數字不大於0,取0,其他情況取-1。

顯然因為模式匹配不是表達式,我得先包裝成一個函數才能很好的使用,如果用匿名函數,還需要更加麻煩的委託類型轉換什麼的。但是如果模式匹配語法是表達式,則這個問題根本就不存在。

所以,我覺得的模式匹配的語法應該長這樣:

var result = case input
when null =&> null
when int number =&> case
when number &> 0 =&> 1
default =&> 0//default標識case結束,或者用括弧來明確case範圍。
default =&> -1;

很顯然,由於模式匹配成了表達式,大量冗餘的函數定義什麼的東西都可以省略。並且即使是表達式,合理的運用語法也能解決嵌套的問題。

default關鍵詞可以換成when default/otherwise/else都可以。

下面 Mx.T 給出的例子又是一個非常漂亮的說明模式匹配應該是表達式的例子,因為那個例子改成模式匹配表達式是這樣的:

var message = case shape
when null =&> throw new ArgumentNullException(nameof(shape))
when Circle c =&> $"circle with radius {c.Radius}"
when Rectangle r =&> case
when r.Length == r.Height =&> $"{r.Length} x {r.Height} square"
otherwise =&> $"{r.Length} x {r.Height} rectangle"
otherwise =&> "&";
Console.WriteLine( message );

很顯然比switch...case的形式漂亮多了。


可以看這裡啊 https://github.com/dotnet/roslyn/blob/features/patterns/docs/features/patterns.md

C# 的 Pattern 可以是 Switch Statement, 可以是 Match Expression, 也可以是 Case Expression, 還可以是 Throw Expression.

那為什麼題主只看到這麼一個雞肋的 Switch Statement 呢?

因為:https://github.com/dotnet/roslyn/issues/2136

Pattern matching, part I (type patterns in switch statements and is-expressions) (subset of Proposal: #206 Pattern Matching)

關注過 C#7 開發過程的人就知道,C#7 作為 roslyn 開源後在 Github 上集思廣益完成的一個版本,大家提出了一大堆特性……然而最後砍砍砍,所剩無幾。也就比隔壁 C++17 好點吧。

都是辣雞
—— LYP

順便貼一下 Pattern Matching 的完成體應該是什麼樣子:

var areas =
from primitive in primitives
let area = primitive switch (
case Line l: 0,
case Rectangle r: r.Width * r.Height,
case Circle c: Math.PI * c.Radius * c.Radius,
case *: throw new ApplicationException()
)
select new { Primitive = primitive, Area = area };

至於另外一個答案里說的類型問題,Proposal 里是這麼說的:

The type of the match_expression is the best common type of the expressions appearing to the right of the : tokens of the match sections.


dotnet/csharplang 其實就是一本草稿紙,大家在上面亂塗。等到真正出來的時候,都已經面目全非了。所以不要太關心裏面寫了啥。


switch實在是太丑。

如我所聞,原來的C#7.0 在 應該是這樣的:

The week in .NET – Visual Studio 2017, .NET Core SDK, F# 4.1, On .NET with Phillip Carter, Happy Birthday from John Shewchuk, FNA, Pyre

https://www.youtube.com/watch?v=xa2WHKpVt_samp;amp;amp;amp;amp;amp;t=2993samp;amp;amp;amp;amp;amp;list=PLpCsCyd254FpFB-O4mW6q8z0lEqwdIuXaamp;amp;amp;amp;amp;amp;index=2

2017年3月3日發布
You"ll learn the latest on .NET Core 2.0 and C# 7, directly from a member of the team. I"ll start with a quick refresher course on .NET Core 1 and C# 6, then I"ll show the latest features, give a timeline, and finally talk about what may come after what"s next.

更優雅的匹配:

不可空類型:

來自F#的記錄和可區分類型:

簡潔的主構造函數:

來自Java8 的介面默認實現:

這才是我們想要的。

借地方再吐槽一下out 新加的語法:

if (!int.Parse(str, out var a) {
return;
}
use(a);
//...

這種語法就會導致有人很容易就寫出這麼奇葩的代碼來,很不符合直覺。為什麼不設定a的使用作用域只能在 if 的大括弧裡面,限制它的使用?

我認為理想的語法應該這樣:

out a; // 可以省略長的類型聲明
if (!int.Parse(str, out a) {
return;
}
use(a);
//...


我記得很早以前擴展方法剛出時,我就用擴展方法實現了switch的擴展,採用鏈式編程,效果和你期望的差不多,所以其實你也是可以自己嘗試實現的。

代碼差不多是這樣的:

var B=A.Switch()

.CaseReturn(lambada判定表達式,返回值)

.CaseReturn(lambada判定表達式,lambada表達式)

.CaseRun(lambada判定表達式,lambada表達式)

.DefaultReturn(返回值)

.ReturnValue;

繼而還可以實現CaseInReturn和CaseIsReturn之類的,會更加方便。


合理的理由有很多個,至於哪個是最終答案我也說不清楚。只能把這些合理的理由都說出來。首先,在c#的演進里大家都發現了,增加關鍵字是被嚴格控制的,從c#1到7那麼多個版本里增加的關鍵字數量極其少,一般都是對現有關鍵字進行重用並且要保證他們功能之間有很接近或者有巨大的區別,避免歧義的出現。所以你這裡說要增加新的關鍵字和模式是不符合c#的觀念的。第二,這個模式匹配其實只是初版,在c#的furture里這個叫做低級模式匹配,高級的都被吃掉了,詳情查看以下鏈接https://github.com/dotnet/roslyn/issues/10866

https://github.com/dotnet/roslyn/pull/10888

這說明這個特性還不算完全甚至可以說他還不配叫這個名字,說不定未來可能有broken的思路出現(雖然可能性很低,但是也算一種可能)。第三就是這個類型推斷的問題,如果你要實現表達式樣式的模式,我們可能根本不需要返回值,結果也要寫個返回值,因為畢竟要要有類型推斷,總要提供返回值才能滿足推斷的需求。

當然,你這個在語法上肯定是更簡潔的,不過用了很多你這樣又要增加編譯器的複雜度了,還可能帶來歧義。而且你這樣寫只是因為你個body還算簡單,lambda寫起來還挺漂亮,body複雜了還不是一個鬼樣。而目前版本只是對switch case的一個拓展,滿足上面所說的三條理由,不增加額外關鍵字,這是一個拓展,方便實現各種帶返回值不帶返回值的匹配。

另外我想問一下為什麼要變成一個函數,你直接前面前置一個變數來接受這個值一樣可以。另外就是case when語法確實被支持,所以你可以寫什麼case int number when number&>0這樣的玩意。

====================================

二維碼

====================================

(沒人邀我,好痛苦,不能寫瀉藥)

====================================

不用看了,只會改錯,不會更新


強答一下

感覺題主提到的方式似乎存在問題? 編譯器要強行推斷出result的一個合理的類型似乎是比較棘手的?看一遍所有case的類型,然後做出一個最合理的推斷?還是用object類型大包大攬?用了object類型來包裝了,那之後再類型轉換?那這個語句的意義還剩下什麼…

當然對於題主談到的情況來說,也許能推斷成 int? ,或者說我們直接指定成int?,編譯器只負責檢查就好了,但是對於官方一直以來用到的樣例,

switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("&");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}

不返回什麼東西,只是為各自的情況做些不一樣的事情,這個該怎麼處理?

就像是這個式子 var result = predication?exp1:exp2; 不也是無法處理 「只想在exp處做些什麼而需要什麼返回值」的情況,我們不也是用著if-else「活得好好的」,2333333

水平有限,可能有很多疏漏甚至錯誤,歡迎指正和討論。


怎麼說呢,7.0就元組和try*不錯。


感覺不如6.0中switch 語法簡潔


推薦閱讀:

C# 中為什麼List<List<T>> 不能轉換為 IList<IList<T>> ?
為什麼很多人認為.NET就是拖控制項?
C#為什麼總不被看好?
如何提高unity開發水平?

TAG:C# |