我见过在 .Net 4 中引入的元组(Tuple),但我无法想象它可以在哪里使用。我们总是可以制作自定义类或结构体。
这就是重点——并不总是需要创建自定义类或结构体更加方便。这就像Action
或Func
一样的改进……你可以自己创建这些类型,但是它们在框架中存在非常方便。
=new..()
来创建自定义类实例吗? - user786使用元组,你可以轻松实现一个二维字典(或者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
以下是一个简单的例子 - 假设你有一个方法需要查找用户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");
}
Tuple<bool, T> TryParse<T>(string input)
,而无需使用输出参数,在元组中获取两个值返回。 - Tejs我用一个元组来解决欧拉计划第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);
}
}
对于这个,我本来可以使用自定义类型的,但它看起来会和元组一模一样。
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)。
new Tuple<Guid, string, ...>
。 - AaronLS我不喜欢滥用它们,因为它们生成的代码无法自我解释,但是它们非常适合实现即时复合键,因为它们实现了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));
}
元组非常适用于同时执行多个异步IO操作并一起返回所有值。以下是使用元组进行和不使用元组进行的示例。实际上,元组可以使您的代码更清晰!
没有元组(嵌套可怕!):
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#程序员。
我倾向于避免在大多数情况下使用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
,有几个选项:
Dictionary<string,Dictionary<string,int>>
。Dictionary<CarAndCity,int>
。Dictionary<Tuple<string, string>,int>
。Tuple
很好的使用方式。元组在函数式语言中被广泛使用,可以做更多的事情。现在F#是一个“官方”的.NET语言,您可能想要从C#与之互操作,并在两种语言编写的代码之间传递它们。