19 ответов:
вот в чем дело-так удобнее не чтобы сделать пользовательский класс или struct все время. Это улучшение, как
ActionилиFunc... вы можете сделать эти типы самостоятельно, но удобно, что они существуют в рамках.
с кортежами вы можете легко реализовать двумерный словарь (или 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. Выбор между типом значения и ссылочным типом особенно интересен.
Как видно из статьи, движущей силой Кортежа было так много групп внутри Microsoft, которые использовали его, команда F# спереди. Хотя и не упоминается, я считаю, что новое" динамическое " ключевое слово в C# (и VB.NET) имел что-то с этим связано, кортежи очень распространены в динамических языках.
в противном случае он не особенно превосходит создание собственного poco, по крайней мере, вы можете дать членам лучшее имя.
обновление: из-за большой ревизии в версии C# 7, Теперь становится намного больше синтаксиса любви. Предварительное объявление в этот блог.
я использовал Кортеж для решения задача 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); } }Я может использовали пользовательский тип для этого, но это выглядело бы ровно как кортеж.
вот небольшой пример-скажем, у вас есть метод, который должен искать дескриптор пользователя и адрес электронной почты, учитывая идентификатор пользователя. Вы всегда можете создать пользовательский класс, содержащий эти данные, или использовать параметр 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"); }
в 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));вместо объединения коллекции значений в один результат, давайте развернем один результат в a коллекция ценностей. Самый простой способ написать эту функцию:
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 (чтобы использовать как для поиска, так и для целей заказа).
и они объединяют все хэш-коды своих элементов, внутренне; например, вот 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)); }
кортежи отлично подходят для выполнения нескольких асинхронных операций ввода-вывода одновременно и возврата всех значений вместе. Вот примеры выполнения этого с кортежем и без него. Кортежи действительно могут сделать ваш код более четким!
без (неприятных гнездования!):
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.
несколько примеров с моей головы:
- расположение X и Y (и Z, Если хотите)
- ширина и высота
- все, что измеряется с течением времени
например, вы не хотите включать систему.Рисование в веб-приложении только для использования Point/PointF и Size / SizeF.
вы должны быть очень осторожны с использованием
Tupleи, вероятно, подумайте дважды, прежде чем сделать это. Из моего предыдущего опыта я узнал, что с помощьюTupleделает код очень трудным для чтения и поддержки в будущем. Некоторое время назад мне пришлось исправить некоторый код, где кортежи использовались почти везде. Вместо того, чтобы думать о правильных объектных моделях, они просто использовали кортежи. Это был кошмар... иногда мне хотелось убить парня, который написал код...не хочу сказать, что вы не следует использовать
Tupleи это зло или что-то и я на сто процентов уверен, что есть некоторые задачи, гдеTupleявляется лучшим кандидатом для использования, но, вероятно, вы должны подумать еще раз, вам действительно это нужно?
лучшее использование для кортежей, которые я нашел, - это когда нужно вернуть более 1 типа объекта из метода, вы знаете, какие типы объектов и число они будут, и это не длинный список.
другие простые альтернативы будут использовать параметр "out"
private string MyMethod(out object)или словарь
Dictionary<objectType1, objectType2>однако использование кортежа сохраняет либо создание объекта "out", либо необходимость существенно искать запись в словаре;
только что нашел решение одной из моих проблем в кортеж. Это похоже на объявление класса в области метода, но с ленивым объявлением имен его полей. Вы работаете с коллекциями кортежей, его единичными экземплярами, а затем создаете коллекцию анонимного типа с требуемыми именами полей на основе вашего кортежа. Это позволяет избежать создания нового класса для этой цели.
задача состоит в том, чтобы написать ответ JSON от LINQ без каких-либо дополнительных классы:
//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);конечно, мы могли бы сделать это с объявлением нового класса для моих групп, но идея создать такие анонимные коллекции без объявления новых классов.
Ну в моем случае, мне пришлось использовать Кортеж, когда я узнал, что мы не можем использовать параметр в асинхронный метод. Читайте об этом здесь. Мне также нужен другой тип возврата. Поэтому я использовал Кортеж вместо моего типа возврата и пометил метод как асинхронный.
ниже пример кода.
... ... // 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; }
параметр, хорошо, когда есть только несколько значений, которые должны быть возвращены, но когда вы начинаете сталкиваться с 4, 5, 6 или более значений, которые должны быть возвращены, это может стать громоздким. Другой вариант для возврата нескольких значений-создать и вернуть определяемый пользователем класс / структура или использовать Кортеж для упаковки всех необходимых значений быть возвращенным методом.
первый вариант, используя класс / структуру для возврата значений, прост. Просто создавать тип (в данном примере это структура) выглядит так:
public struct Dimensions { public int Height; public int Width; public int Depth; }второй вариант, используя Кортеж, является еще более элегантным решением, чем использование userdefined объект. Кортеж может быть создан для хранения любого количества значений различных типов. Кроме того, данные, которые вы храните в кортеже, неизменяемы; как только вы добавляете данные Кортеж через конструктор или статический метод 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, и я нашел вариант использования для кортежей.
работа с динамическими данными в веб-проектах иногда может быть болью при отображении и т. д.
Мне нравится, как кортеж просто автоматически отображается на item1, item2, itemN, который кажется мне более надежным, чем использование индексов массива, где вы можете попасть в элемент out of index или использовать анонимный тип, где вы можете неправильно написать имя свойства.
это похоже на 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 }; } } } }
Comments