可以在.Net 4.0中使用Tuple的实际示例?

我已经看到.Net 4中引入的Tuple,但我无法想象它可以在哪里使用。 我们总是可以做一个自定义的类或结构。

这就是要点 – 不要总是自定义类或结构。 这是一个类似于Action或者Func的改进…你可以自己创build这个types,但是它们存在于框架中很方便。

使用元组,你可以很容易地实现一个二维字典(或n维)。 例如,您可以使用这样的字典来实现货币兑换映射:

 var forex = new Dictionary<Tuple<string, string>, decimal>(); forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR forex.Add(Tuple.Create("USD", "GBP"), 0.64128m); forex.Add(Tuple.Create("EUR", "USD"), 1.33635m); forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m); forex.Add(Tuple.Create("GBP", "USD"), 1.55938m); forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m); forex.Add(Tuple.Create("USD", "USD"), 1.00000m); forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m); forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m); decimal result; result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20 result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99 result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58 

在MSDN杂志上有一篇很好的文章 ,谈到在BCL中joinTuple的痛苦和devise考虑。 在值types和引用types之间进行select尤其有趣。

正如文章所说明的那样,Tuple背后的推动力量是微软内部的这么多团体对F#团队的使用。 虽然没有提到,但我认为C#(和VB.NET)中新的“dynamic”关键字也与它有关,元组在dynamic语言中非常常见。

否则,创build自己的poco并不是特别优越,至less可以给会员一个更好的名字。


更新:由于在C#版本7的一个大修改,现在得到更多的语法爱。 在这个博客文章初步公布。

这里有一个小例子 – 假设你有一个方法需要查找用户的句柄和电子邮件地址,给定一个用户ID。 您始终可以创build一个包含该数据的自定义类,或者为该数据使用ref / out参数,或者您可以返回一个Tuple并拥有一个不错的方法签名,而无需创build新的POCO。

 public static void Main(string[] args) { int userId = 0; Tuple<string, string> userData = GetUserData(userId); } public static Tuple<string, string> GetUserData(int userId) { return new Tuple<string, string>("Hello", "World"); } 

我用一个元组来解决Euler项目的问题11 :

 class Grid { public static int[,] Cells = { { 08, 02, 22, // whole grid omitted public static IEnumerable<Tuple<int, int, int, int>> ToList() { // code converts grid to enumeration every possible set of 4 per rules // code omitted } } 

现在我可以解决整个问题:

 class Program { static void Main(string[] args) { int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4); Console.WriteLine("Maximum product is {0}", product); } } 

可以使用自定义types,但它看起来就像Tuple

C#的元组语法是荒谬的庞大,所以元组是痛苦的申报。 而且它没有模式匹配,所以使用起来也很痛苦。

但是偶尔,你只需要一个特别的对象分组,而不需要为它创build一个类。 例如,假设我想汇总一个列表,但是我想要两个值而不是一个:

 // sum and sum of squares at the same time var x = Enumerable.Range(1, 100) .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x)); 

我们不是将一组值合并成一个结果,而是将一个结果展开成一组值。 写这个函数最简单的方法是:

 static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f) { Tuple<T, State> res; while ((res = f(seed)) != null) { yield return res.Item1; seed = res.Item2; } } 

f将一些状态转换成一个元组。 我们从元组返回第一个值,并将新的状态设置为第二个值。 这使我们能够在整个计算过程中保持状态。

你这样使用它:

 // return 0, 2, 3, 6, 8 var evens = Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null) .ToList(); // returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 var fibs = Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2))) .Take(10).ToList(); 

evens相当简单,但是fibs更聪明一些。 它的state实际上是一个分别持有fib(n-2)和fib(n-1)的元组。

我不喜欢它们的滥用,因为它们产生的代码并不能解释自己,但是它们实现了即时复合键,因为它们实现了IStructuralEquatable和IStructuralComparable(用于查找和sorting目的)。

他们在内部将所有项目的哈希码组合起来; 例如,这里是Tuple的GetHashCode(取自ILSpy):

  int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) { return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3)); } 

元组很适合一次执行多个asynchronousIO操作,并将所有值一起返回。 这里是使用和不使用Tuple的例子。 元组实际上可以使你的代码更清晰!

没有(讨厌的嵌套!):

 Task.Factory.StartNew(() => data.RetrieveServerNames()) .ContinueWith(antecedent1 => { if (!antecedent1.IsFaulted) { ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result); Task.Factory.StartNew(() => data.RetrieveLogNames()) .ContinueWith(antecedent2 => { if (antecedent2.IsFaulted) { LogNames = KeepExistingFilter(LogNames, antecedent2.Result); Task.Factory.StartNew(() => data.RetrieveEntryTypes()) .ContinueWith(antecedent3 => { if (!antecedent3.IsFaulted) { EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result); } }); } }); } }); 

与Tuple

 Task.Factory.StartNew(() => { List<string> serverNames = data.RetrieveServerNames(); List<string> logNames = data.RetrieveLogNames(); List<string> entryTypes = data.RetrieveEntryTypes(); return Tuple.Create(serverNames, logNames, entryTypes); }).ContinueWith(antecedent => { if (!antecedent.IsFaulted) { ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1); LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2); EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3); } }); 

如果你使用的是隐式types的匿名函数,那么你不会使用Tuple来减less代码的清晰度。 从一个方法重新调整一个元组? 在我看来,代码清晰是关键的时候谨慎使用。 我知道C#中的函数式编程很难抵制,但我们必须考虑所有那些笨重的“面向对象”的C#程序员。

我倾向于避免Tuple在大多数情况下,因为它伤害可读性。 但是,当您需要对不相关的数据进行分组时, Tuple非常有用。

例如,假设你有一个汽车列表和他们购买的城市:

 Mercedes, Seattle Mustang, Denver Mercedes, Seattle Porsche, Seattle Tesla, Seattle Mercedes, Seattle 

您想要汇总每个城市每辆车的计数:

 Mercedes, Seattle [3] Mustang, Denver [1] Porsche, Seattle [1] Tesla, Seattle [1] 

要做到这一点,你创build一个Dictionary 。 你有几个select:

  1. 创build一个Dictionary<string, Dictionary<string, int>>
  2. 创build一个Dictionary<CarAndCity, int>
  3. 创build一个Dictionary<Tuple<string, string>, int>

第一个选项会丢失可读性。 这将需要你写更多的代码。

第二个选项工作简洁,但是汽车和城市并没有真正的联系,可能不属于一个class级。

第三个选项简洁干净。 这是Tuple的一个很好的用法。

元组在函数式语言中被大量使用,可以对它们做更多的事情,现在F#是一种'官方'.net语言,你可能想要在C#中进行交互操作,并将它们在两种语言编写的代码之间传递。

我的头顶上有几个例子:

  • 一个X和Y的位置(如果你喜欢Z的话)
  • 宽度和高度
  • 任何测量的时间

例如,您不希望将System.Drawing包含在Web应用程序中,只是使用Point / PointF和Size / SizeF。

在使用Tuple应该非常小心,在做这件事之前可能要三思而后行。 从我以前的经验中,我发现使用Tuple使代码在将来很难阅读和支持。 前一段时间,我不得不修正一些代码,几乎在任何地方使用元组。 他们没有考虑适当的对象模型,而是使用元组。 这是噩梦…有时我想杀死编写代码的人…

不要说你不应该使用Tuple ,它是邪恶的或什么的,我百分之百确定有一些任务, Tuple是最好的候选人使用,但可能你应该再想一想,你真的需要它?

我已经发现的元组的最佳用法是当需要从一个方法返回多于一种types的对象时,你知道它们将是什么对象types和数量,并且它不是一个长列表。

其他简单的替代方法是使用“out”参数

 private string MyMethod(out object) 

或制作一个词典

 Dictionary<objectType1, objectType2> 

然而,使用元组可以保存创build'out'对象或者必须实质地查找字典中的条目;

刚刚在Tuple中find了我的一个问题的解决scheme。 这就像在一个方法的范围内声明一个类,而是用它的域名的惰性声明。 您可以使用集合的单个实例进行操作,然后根据您的元组创build一个具有所需字段名称的匿名types集合。 这样可以避免为此创build新类。

任务是从LINQ写一个JSON响应,不需要任何额外的类:

  //I select some roles from my ORM my with subrequest and save results to Tuple list var rolesWithUsers = (from role in roles select new Tuple<string, int, int>( role.RoleName, role.RoleId, usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count() )); //Then I add some new element required element to this collection var tempResult = rolesWithUsers.ToList(); tempResult.Add(new Tuple<string, int, int>( "Empty", -1, emptyRoleUsers.Count() )); //And create a new anonimous class collection, based on my Tuple list tempResult.Select(item => new { GroupName = item.Item1, GroupId = item.Item2, Count = item.Item3 }); //And return it in JSON return new JavaScriptSerializer().Serialize(rolesWithUsers); 

因为我们可以通过为我的组声明一个新的类来做到这一点,但是创build这样一个一致的集合而不声明新类的想法。

那么在我的情况下,我不得不使用一个元组,当我发现我们不能用asynchronous方法中的参数。 在这里阅读。 我还需要一个不同的返回types。 所以我使用了一个Tuple作为我的返回types,并将其标记为async。

下面的示例代码。

 ... ... // calling code. var userDetails = await GetUserDetails(userId); Console.WriteLine("Username : {0}", userDetails.Item1); Console.WriteLine("User Region Id : {0}", userDetails.Item2); ... ... private async Tuple<string,int> GetUserDetails(int userId) { return new Tuple<string,int>("Amogh",105); // Note that I can also use the existing helper method (Tuple.Create). } 

在这里了解更多关于元组 希望这可以帮助。

当您需要通过线路发送对象或传递到不同的应用程序层并将多个对象合并为一个对象时,更改对象的形状:

例:

 var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails(); 

ExtensionMethod:

 public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress) { var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null; var customerDetails = new CustomerDetails { FirstName = customerAndAddress.Item1.Name, LastName = customerAndAddress.Item1.Surname, Title = customerAndAddress.Item1.Title, Dob = customerAndAddress.Item1.Dob, EmailAddress = customerAndAddress.Item1.Email, Gender = customerAndAddress.Item1.Gender, PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone) }; if (mainAddress != null) { customerDetails.AddressLine1 = !string.IsNullOrWhiteSpace(mainAddress.HouseName) ? mainAddress.HouseName : mainAddress.HouseNumber; customerDetails.AddressLine2 = !string.IsNullOrWhiteSpace(mainAddress.Street) ? mainAddress.Street : null; customerDetails.AddressLine3 = !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null; customerDetails.AddressLine4 = !string.IsNullOrWhiteSpace(mainAddress.County) ? mainAddress.County : null; customerDetails.PostCode = mainAddress.PostCode; } ... return customerDetails; } 

只有less数需要返回的值时,out参数非常好,但是当您遇到需要返回的4,5,6或更多个值时,它可能会变得笨拙。 另一个返回多个值的选项是创build并返回一个用户定义的类/结构,或者使用一个Tuple来打包所有需要由方法返回的值。

第一个选项,使用类/结构来返回值,很简单。 只要创buildtypes(在这个例子中它是一个结构)就像这样:

 public struct Dimensions { public int Height; public int Width; public int Depth; } 

第二个select,使用Tuple,比使用用户定义的对象更加优雅。 可以创build一个Tuple来容纳任意数量的不同types的值。 另外,你在Tuple中存储的数据是不可变的。 一旦通过构造函数或静态Create方法将数据添加到Tuple中,则无法更改该数据。 元组可以接受最多8个不同的值。 如果你需要返回8个以上的值,你将需要使用特殊的Tuple类:Tuple Class当创build一个具有8个以上值的Tuple时,你不能使用静态的Create方法,而必须使用类的构造函数。 这是你将如何创build一个10个整数值的元组:

 var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> ( 1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10)); 

当然,你可以继续添加更多元组到每个embedded元组的末尾,创build你需要的任何大小的元组。

只有原型 – 元组是没有意义的。 它使用方便,但它只是一个捷径! 对于原型 – 很好。 只要确保稍后删除此代码。

写起来容易,难以阅读。 它与类,内部类,匿名类等没有明显的优势

那么我尝试了3种方法来解决在C#7中的相同的问题,我已经find了元组的用例。

在web项目中使用dynamic数据有时候在映射时会很痛苦

我喜欢Tuple只是自动映射到item1,item2,itemN的方式,这比使用数组索引可能在索引项目外被捕获或使用可能拼错属性名称的匿名types更加健壮。

感觉就像一个DTO是通过使用一个Tuple免费创build的,而且我可以使用itemN来访问所有的属性,而不必为此创build一个单独的DTO。

 using System; namespace Playground { class Program { static void Main(string[] args) { var tuple = GetTuple(); Console.WriteLine(tuple.Item1); Console.WriteLine(tuple.Item2); Console.WriteLine(tuple.Item3); Console.WriteLine(tuple); Console.WriteLine("---"); var dyn = GetDynamic(); Console.WriteLine(dyn.First); Console.WriteLine(dyn.Last); Console.WriteLine(dyn.Age); Console.WriteLine(dyn); Console.WriteLine("---"); var arr = GetArray(); Console.WriteLine(arr[0]); Console.WriteLine(arr[1]); Console.WriteLine(arr[2]); Console.WriteLine(arr); Console.Read(); (string, string, int) GetTuple() { return ("John", "Connor", 1); } dynamic GetDynamic() { return new { First = "John", Last = "Connor", Age = 1 }; } dynamic[] GetArray() { return new dynamic[] { "John", "Connor", 1 }; } } } }