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

时间:2010-04-30 14:58:43

标签: c# .net vb.net .net-4.0 c#-4.0

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

19 个答案:

答案 0 :(得分:82)

这就是重点 - 更方便一直制作自定义类或结构。这是一个像ActionFunc这样的改进...你可以自己创建这些类型,但它们存在于框架中很方便。

答案 1 :(得分:74)

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

答案 2 :(得分:25)

MSDN杂志中有一篇excellent article,讨论了将Buple添加到BCL的痛苦和设计考虑因素。在值类型和引用类型之间进行选择特别有趣。

正如文章明确指出的那样,Tuple背后的驱动力是微软内部的许多团体都在使用它,F#团队在前面。虽然没有提到,但我认为C#(和VB.NET)中新的“动态”关键字也与它有关,元组在动态语言中很常见。

除此之外,创建自己的poco并不是特别优越,至少可以给会员一个更好的名字。


更新:由于C#版本7的重大修订,现在获得了更多的语法爱。 this blog post中的初步公告。

答案 3 :(得分:23)

我用一个元组来解决Problem 11 of Project Euler

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

可以为此使用自定义类型,但它看起来与完全类似于元组

答案 4 :(得分:22)

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

答案 5 :(得分: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)。

答案 6 :(得分: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 :(得分:6)

元组非常适合一次执行多个异步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#程序员。

答案 8 :(得分:5)

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

答案 9 :(得分: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>
  4. 第一个选项丢失了可读性。它需要你写更多的代码。

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

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

答案 10 :(得分:4)

我头顶的几个例子:

  • X和Y位置(如果您愿意,可以选择Z)
  • 宽度和高度
  • 随时间测量的任何事情

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

答案 11 :(得分:2)

使用Tuple时应该非常小心,在此之前可能会三思而后行。根据我之前的经验,我发现使用Tuple会使代码在将来很难阅读和支持。不久之前,我不得不修复一些几乎无处不在的元组使用的代码。他们只是使用元组而不是考虑正确的对象模型。那是噩梦...有时候我想杀掉编写代码的那个人......

不要说你不应该使用Tuple以及它的邪恶或其他东西,我百分之百确定有一些任务在哪里{{{ 1}}是最好的候选人,但你可能应该再想一想,你真的需要吗?

答案 12 :(得分:1)

我发现的元组的最佳用途是当需要从方法中返回多于一种类型的对象时,您知道它们将是什么对象类型和数字,并且它不是很长的列表。

其他简单的替代方案是使用'out'参数

private string MyMethod(out object)

或制作字典

Dictionary<objectType1, objectType2>

使用Tuple可以保存创建'out'对象或者基本上查找字典中的条目;

答案 13 :(得分: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);

原因我们可以通过为我的组声明一个新的类来实现这一点,但是想要在不声明新类的情况下创建这样一个无穷无尽的集合。

答案 14 :(得分:1)

在我的情况下,当我发现我们不能在异步方法中使用out参数时,我不得不使用Tuple。阅读它here。我还需要一种不同的返回类型。所以我使用了一个Tuple作为我的返回类型,并将该方法标记为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).
}

详细了解Tuple here。 希望这会有所帮助。

答案 15 :(得分:0)

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

示例:

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

答案 16 :(得分:0)

当只有少数值需要返回时,out参数很棒, 但是当您开始遇到需要返回的4,5,6或更多值时,它就会出现 会变得笨拙。返回多个值的另一个选项是创建和返回 用户定义的类/结构或使用元组打包所需的所有值 通过方法返回。

使用类/结构返回值的第一个选项很简单。只是 创建类型(在此示例中,它是一个结构),如下所示:

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

使用Tuple的第二个选项是比使用用户定义更优雅的解决方案 宾语。可以创建一个元组来保存任意数量的不同类型的值。 此外,您存储在元组中的数据是不可变的;一旦你添加数据 通过构造函数或静态Create方法的元组,该数据不能 改变。 元组可以接受最多包括八个单独的值。如果你需要返回 超过八个值,您将需要使用特殊的Tuple类: 元组类 创建具有八个以上值的元组时,不能使用静态创建 方法 - 您必须改为使用类的构造函数。这就是你的意思 创建一个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));

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

答案 17 :(得分:0)

仅用于原型设计 - 元组无意义。它使用起来很方便,但它只是一个捷径!对于原型 - 很好。请务必稍后删除此代码。

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

答案 18 :(得分:0)

我尝试了3种方法来解决C#7中的同一问题,我找到了一个用于元组的用例。

在映射等时,处理Web项目中的动态数据有时会很麻烦。

我喜欢Tuple刚刚自动映射到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 };
            }
        }
    }
}