1. 老版本代碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var fullName = GetFullName(); 6 7 Console.WriteLine(fullName.Item1);// Item1,2,3不能忍,,, 8 Console.WriteLine(fullName.Item2); 9 Console.WriteLine(fullName.Item3);10 }11 static Tuple<string, string, string> GetFullName() => new Tuple<string, string, string>("first name", "blackheart", "last name");12 }
在有些情境下,我們需要一個方法返回一個以上的傳回值,微軟在.NET 4中引入了Tuple這個泛型類,可以允許我們返回多個參數,每個參數按照順序被命名為 Item1;Item2,Item3 ,算是部分的解決了我們的問題,但是對於強迫症程式員來說,Item1,2,3的命名簡直是不能忍的,,,so,在C#7中,引入了一個新的泛型型別ValueTuple<T>來解決這個問題,這個類型位於一個單獨的dll(System.ValueTuple)中,可以通過nuget來引入到你當前的項目中(http://www.php.cn/)。
2. ValueTuple
不廢話,直接看代碼:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var fullName = GetFullName(); 6 7 Console.WriteLine(fullName.First); // 終於可以不是Item1,2,3了,,, 8 Console.WriteLine(fullName.Middle); 9 Console.WriteLine(fullName.Last);10 }11 12 static (string First, string Middle, string Last) GetFullName() => ("first name", "blackheart", "last name");13 }
看出來差別了嗎?我們終於可以用更直觀的名字來替換掉該死的"Item1,2,3"了,看起來很棒吧。但是貌似我們並沒有用到上面我提到的System.ValueTuple,我們翻開編譯後的程式集看看:
1 internal class Program 2 { 3 private static void Main(string[] args) 4 { 5 ValueTuple<string, string, string> fullName = Program.GetFullName(); 6 Console.WriteLine(fullName.Item1); // 原來你還是Item1,2,3,,,FUCK!!! 7 Console.WriteLine(fullName.Item2); 8 Console.WriteLine(fullName.Item3); 9 }10 11 [TupleElementNames(new string[]12 {13 "First",14 "Middle",15 "Last"16 })]17 private static ValueTuple<string, string, string> GetFullName()18 {19 return new ValueTuple<string, string, string>("first name", "blackheart", "last name");20 }21 }
不看不知道,一看嚇一跳,原來我們的 fullName.First; 編譯後居然還是 fullName.Item1 ,真是日了狗了。。。
不同之處在於GetFullName這個方法,編譯器把我們簡化的文法形式翻譯成了 ValueTuple<string, string, string> ,還給加了一個新的Attribute(TupleElementNamesAttribute),然後把我們自訂的非常直觀友好的“First”,"Middle","Last"當作中繼資料給存起來了(如果只是局部使用,則不會添加這樣的中繼資料)。TupleElementNamesAttribute和ValueTuple一樣,位於System.ValueTuple的單獨dll中。
3. Example
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var range = (first: 1, end: 10); 6 //也可以這樣寫,效果是一樣的,編譯後都是沒有了first,end的痕迹,,,first和end只是文法層面的障眼法 7 //(int first, int last) range = (1, 10); 8 Console.WriteLine(range.first); 9 Console.WriteLine(range.end);10 11 //可以使用var,這種無顯示聲明一個變數的方式會編譯出多餘的代碼,慎用,不知是不是還未最佳化好。12 (var begin, var end) = (DateTime.Parse("2017-1-1"), DateTime.Parse("2017-12-31"));13 Console.WriteLine(begin);14 Console.WriteLine(end);15 16 //begin,end可以被覆蓋重新命名為startDate和endDate,但是會有一個編譯警告,提示名字被忽略掉了。17 //warning CS8123: The tuple element name 'begin' is ignored because a different name is specified by the target type '(DateTime startDate, DateTime endDate)'18 //warning CS8123: The tuple element name 'end' is ignored because a different name is specified by the target type '(DateTime startDate, DateTime endDate)‘19 (DateTime startDate, DateTime endDate) timeSpan = (begin: DateTime.Parse("2017-1-1"), end: DateTime.Parse("2017-12-31"));20 Console.WriteLine(timeSpan.startDate);21 Console.WriteLine(timeSpan.endDate);22 }23 }
look一下編譯後的代碼:
1 private static void Main(string[] args) 2 { 3 ValueTuple<int, int> range = new ValueTuple<int, int>(1, 10); 4 Console.WriteLine(range.Item1); 5 Console.WriteLine(range.Item2); 6 ValueTuple<DateTime, DateTime> expr_3C = new ValueTuple<DateTime, DateTime>(DateTime.Parse("2017-1-1"), DateTime.Parse("2017-12-31")); 7 DateTime item = expr_3C.Item1; 8 DateTime item2 = expr_3C.Item2; 9 DateTime begin = item;10 DateTime end = item2;11 Console.WriteLine(begin);12 Console.WriteLine(end);13 ValueTuple<DateTime, DateTime> timeSpan = new ValueTuple<DateTime, DateTime>(DateTime.Parse("2017-1-1"), DateTime.Parse("2017-12-31"));14 Console.WriteLine(timeSpan.Item1);15 Console.WriteLine(timeSpan.Item2);16 }
注意 (var begin, var end) = (DateTime.Parse("2017-1-1"), DateTime.Parse("2017-12-31")); 這一行的便宜結果,看起來很是糟糕(上述6-10行紅色部分),可能還是編譯最佳化不足的問題吧(release編譯也是如此)。
4. 總結
新的文法形式確實直觀友好了好多,but,本質依然是藉助泛型型別來實現的,同時也需要編譯器對新文法形式的支援。
瞭解了本質是什麼東西之後,以後在項目中環境允許的話,就放心大膽的使用吧(類型ValueTuple可以出現的地方,(first,last)這種新文法形式均可以)。