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來引入到你當前的項目中(https://www.nuget.org/packages/System.ValueTuple/)。
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)這種新語法形式均可以)。
參考:
https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/
https://docs.microsoft.com/en-us/dotnet/articles/csharp/tuples