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


97

我已经看到了.Net 4中引入的元组,但是我无法想象可以在哪里使用它。我们总是可以创建一个Custom类或Struct。


13
也许这是一个很好的地方,说明这个话题很老。看看C#7中发生了什么!
maf-soft

Answers:


83

这就是重点-始终创建自定义类或结构会更方便。这是一个类似于ActionFunc... 的改进,您可以自己创建此类型,但是将它们存在于框架中很方便。


5
可能值得从MSDN指出:“这种类型的任何公共静态成员都是线程安全的。不保证任何实例成员都是线程安全的。
Matt Borja 2015年

好吧,也许语言设计师应该已经可以更轻松地动态创建自定义类型,然后呢?因此,我们可以保留相同的语法,而不用引入另一种语法?
Thomas Eyde

@ThomasEyde正是他们过去15年以来一直在做的事情。这样便可以添加表达式身体强壮的成员,并且现在重视元组。记录样式类差一点在八月这个版本的C#7的削减(此评论前9个月),并可能现身在C#8,还要注意的是的元组提供价值相等,其中纯老班别。早在2002年引入所有这些功能就需要先见之明
Panagiotis Kanavos,

你是什​​么意思convenient not to make a custom class。您的意思是使用创建自定义类实例=new..()吗?
Alex

75

使用元组,您可以轻松实现二维字典(或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

我宁愿使用Enum作为国家的缩写。那可能吗?
扎克2014年

1
当然,它应该可以正常工作,因为枚举是值类型。
MarioVW

@MarioVW也可以使用多维数组来完成。元组有什么不同?
Alex

26

有一篇很棒的文章MSDN杂志上,讨论了将元组添加到BCL中的肚皮设计和设计注意事项。在值类型和引用类型之间进行选择特别有趣。

正如文章所表明的那样,Tuple背后的推动力是Microsoft内部的许多团体都在使用它,即F#团队在前。尽管未提及,但我认为C#(和VB.NET)中新的“动态”关键字也与此有关,元组在动态语言中非常常见。

否则,它并不比创建自己的poco特别优越,至少您可以给成员一个更好的名字。


更新:由于在C#版本7中进行了大的修订,现在获得了更多的语法爱。在此博客文章中的初步公告。


23

这是一个小示例-假设您有一个方法,需要根据用户ID查找用户的句柄和电子邮件地址。您始终可以创建一个包含该数据的自定义类,或对该数据使用ref / out参数,或者您可以仅返回一个Tuple并具有一个不错的方法签名,而不必创建新的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");
}

10
这是一个很好的例子,但是并不能证明使用Tuple的合理性。
阿米塔布2010年

7
元组在这里很合适,因为您要返回不同的值。但是当您返回不同类型的多个值时,元组会发光得多。
Mark Rushakoff 2010年

21
另一个很好的例子是int.TryParse,因为您可以消除输出参数,而使用元组。因此,您可以Tuple<bool, T> TryParse<T>(string input)不必使用输出参数,而将两个值都返回到元组中。
Tejs

3
实际上,这就是从F#调用任何TryParse方法时发生的情况。
乔尔·穆勒

23

我用一个元组解决了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);
    }
}

可以为此使用自定义类型,但是它看起来就像Tuple一样


16

C#的元组语法笨拙,因此很难声明元组。而且它没有模式匹配,因此使用起来也很痛苦。

但是有时候,您只希望临时对对象进行分组,而无需为其创建类。例如,假设我想聚合一个列表,但是我想要两个值而不是一个:

// 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)的元组。


4
+1 Tuple.Create是方便的简写new Tuple<Guid,string,...>
AaronLS 2014年

@Juliet State关键字的用途是什么
irfandar

7

我不喜欢滥用它们,因为它们生成的代码无法自我解释,但是它们能够实现动态复合键,因为它们实现了IStructuralEquatable和IStructuralComparable(可用于查找和排序),因此非常棒目的)。

他们在内部组合了所有商品的哈希码;例如,这是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));
    }

7

元组非常适合一次执行多个异步IO操作并将所有值一起返回。这是使用和不使用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);
                                            }
                                        });
                            }
                        });
            }
        });

与元组

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);
            }
        });

如果您正在使用有一个隐含的类型匿名函数反正那么你是不是让代码使用元组不太清楚。从方法重调元组?以我的愚见,当代码清晰是关键时,请谨慎使用。我知道C#中的函数编程很难抗拒,但是我们必须考虑所有那些老式的笨拙的“面向对象” C#程序员。


5

元组在功能性语言中大量使用,可以用它们做更多的事情,现在F#是一种“官方” .net语言,您可能想通过C#与它进行互操作,并在以两种语言编写的代码之间传递它们。


元组还以一些流行的脚本语言(例如Python和Ruby,也有用于互操作的.Net实现... IronPython / Ruby)的类型构建。
MutantNinjaCodeMonkey

5

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]

为此,您创建一个Dictionary。您有几种选择:

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

第一种选择会失去可读性。它将需要您编写更多代码。

第二种选择有效且简洁,但是汽车和城市并没有真正的联系,并且可能不属于同一类。

第三种选择是简洁明了。是的很好用Tuple


4

举几个例子:

  • X和Y位置(如果需要,还可以选择Z)
  • 宽度和高度
  • 随时间测量的任何东西

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


2
Tuple在紧急情况下与执行图形操作一样方便,Point或者SomeVector可能有用。它突出显示了两个非常紧密相关的值。在读取代码时,我经常会看到名为StartTime和EndTime的属性,但是比日期时间和持续时间更好,Tuple迫使您每次在业务逻辑的这一领域中进行操作时都要同时考虑这两个值。或返回“嘿,数据已更改(布尔),这里是数据(其他类型)”经过大量处理,而不是进行密集的绑定。
莱昂佩尔蒂埃

3

您应该非常小心地使用它,Tuple并且在执行此操作之前可能要三思。根据我以前的经验,我发现使用Tuple代码会使将来的代码阅读和支持变得非常困难。前一段时间,我不得不修复一些几乎在所有地方都使用元组的代码。他们没有考虑适当的对象模型,而是使用元组。那是一场噩梦...有时候我想杀死写代码的那个人...

不想说您不应该使用Tuple它,这是邪恶的或其他的事情,我百分百确信有一些任务Tuple是使用最佳候选者的,但是您可能应该再三考虑,您真的需要吗? ?


1

我发现的元组的最佳用途是当需要从一种方法返回不止一种类型的对象时,您知道它们将是什么对象类型和编号,并且它不是一长串。

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

private string MyMethod(out object)

或制作字典

Dictionary<objectType1, objectType2>

但是,使用元组可以省去创建“ out”对象的过程,也可以省去在字典中查找条目的麻烦。


1

刚刚在Tuple中找到了我的问题之一的解决方案。就像在方法范围内声明类一样,但是对其字段名称进行了延迟声明。您可以使用元组及其单个实例的集合进行操作,然后根据元组创建具有所需字段名称的匿名类型的集合。这样可以避免您为此目的创建新类。

任务是从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);

当然,我们可以通过为我的组声明一个新的Class来做到这一点,但是创建这样一个匿名集合而不声明新的class的想法。


1

在我的情况下,当我发现不能在异步方法中使用out参数时,我必须使用元组。在这里阅读。我还需要其他返回类型。因此,我改用元组作为返回类型,并将该方法标记为异步。

下面的示例代码。

...
...
// 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).
}

在此处阅读有关Tuple的更多信息。希望这可以帮助。


我假设您不想返回匿名类型或数组(或动态内容)
ozzy432836,2017年

0

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

例:

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

扩展方法:

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;
    }

0

当仅需要返回几个值时,out参数非常有用,但是当您遇到遇到需要返回的4、5、6或多个值时,它可能会变得很笨拙。返回多个值的另一种方法是创建并返回用户定义的类/结构,或者使用Tuple打包方法需要返回的所有值。

第一种选择是使用类/结构返回值,方法很简单。只需像这样创建类型(在本示例中为结构):

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

第二种选择是使用元组,它比使用用户定义的对象更为优雅。可以创建一个元组来容纳任意数量的不同类型的值。另外,您存储在元组中的数据是不可变的。通过构造函数或静态Create方法将数据添加到元组后,该数据将无法更改。元组最多可以接受并包括八个单独的值。如果需要返回八个以上的值,则需要使用特殊的Tuple类:Tuple类当创建具有八个以上的值的Tuple时,不能使用静态的Create方法,而必须使用该类的构造函数。这是创建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));

当然,您可以继续在每个嵌入式元组的末尾添加更多元组,从而创建所需的任何大小的元组。


0

仅用于原型制作-元组毫无意义。使用它们很方便,但这只是捷径!对于原型-很好。只要确保稍后删除此代码即可。

它易于编写,难以阅读。与类,内部类,匿名类等相比,它没有明显的优势。


0

好吧,我尝试了3种方法来解决C#7中的相同问题,并且找到了Tuples的用例。

在Web项目中使用动态数据有时在映射等过程中会很痛苦。

我喜欢元组自动映射到item1,item2,itemN的方式,这对我来说似乎比使用数组索引(在数组索引中您可能会陷入索引项外)或使用匿名类型(在其中可能拼写错误的属性名称)更健壮。

感觉像是仅通过使用元组就免费创建了DTO,我可以使用itemN访问所有属性,这更像是静态类型,而无需为此创建单独的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 };
            }
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.