Answers:
这就是重点-始终不创建自定义类或结构会更方便。这是一个类似于Action
或Func
... 的改进,您可以自己创建此类型,但是将它们存在于框架中很方便。
convenient not to make a custom class
。您的意思是使用创建自定义类实例=new..()
吗?
使用元组,您可以轻松实现二维字典(或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)
不必使用输出参数,而将两个值都返回到元组中。
我用一个元组解决了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一样。
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,...>
我不喜欢滥用它们,因为它们生成的代码无法自我解释,但是它们能够实现动态复合键,因为它们实现了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操作并将所有值一起返回。这是使用和不使用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#程序员。
元组在功能性语言中大量使用,可以用它们做更多的事情,现在F#是一种“官方” .net语言,您可能想通过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
。
举几个例子:
例如,您不希望仅使用Point / PointF和Size / SizeF在Web应用程序中包含System.Drawing。
Tuple
在紧急情况下与执行图形操作一样方便,Point
或者SomeVector
可能有用。它突出显示了两个非常紧密相关的值。在读取代码时,我经常会看到名为StartTime和EndTime的属性,但是比日期时间和持续时间更好,Tuple迫使您每次在业务逻辑的这一领域中进行操作时都要同时考虑这两个值。或返回“嘿,数据已更改(布尔),这里是数据(其他类型)”经过大量处理,而不是进行密集的绑定。
刚刚在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的想法。
在我的情况下,当我发现不能在异步方法中使用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).
}
当您需要跨线发送或传递到应用程序的不同层并且多个对象合并为一个时,可以更改对象的形状:
例:
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;
}
当仅需要返回几个值时,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));
当然,您可以继续在每个嵌入式元组的末尾添加更多元组,从而创建所需的任何大小的元组。
好吧,我尝试了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 };
}
}
}
}