Câu trả lời:
Đó là vấn đề - thuận tiện hơn là không phải lúc nào cũng tạo một lớp hoặc cấu trúc tùy chỉnh. Đó là một cải tiến như Action
hoặc Func
... bạn có thể tự tạo các loại này, nhưng thật tiện lợi khi chúng tồn tại trong khuôn khổ.
convenient not to make a custom class
. Ý bạn là tạo cá thể lớp tùy chỉnh bằng cách sử dụng =new..()
?
Với bộ giá trị, bạn có thể dễ dàng triển khai từ điển hai chiều (hoặc n chiều cho vấn đề đó). Ví dụ: bạn có thể sử dụng một từ điển như vậy để triển khai ánh xạ trao đổi tiền tệ:
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
Có một bài báo xuất sắc trên tạp chí MSDN nói về những cân nhắc về thiết kế và đau bụng khi thêm Tuple vào BCL. Việc lựa chọn giữa kiểu giá trị và kiểu tham chiếu là đặc biệt thú vị.
Như bài báo đã làm rõ, động lực đằng sau Tuple là rất nhiều nhóm bên trong Microsoft đã sử dụng nó, nhóm F #. Mặc dù không được đề cập, tôi nghĩ rằng từ khóa "động" mới trong C # (và VB.NET) cũng có liên quan đến nó, các bộ giá trị rất phổ biến trong các ngôn ngữ động.
Mặt khác, nó không có gì đặc biệt vượt trội so với việc tạo poco của riêng bạn, ít nhất bạn có thể đặt cho các thành viên một cái tên tốt hơn.
CẬP NHẬT: do có một bản sửa đổi lớn trong C # phiên bản 7, bây giờ nhận được nhiều cú pháp hơn. Thông báo sơ bộ trong bài đăng trên blog này .
Đây là một ví dụ nhỏ - giả sử bạn có một phương pháp cần tra cứu địa chỉ email và xử lý của người dùng, được cung cấp một Id người dùng. Bạn luôn có thể tạo một lớp tùy chỉnh có chứa dữ liệu đó hoặc sử dụng tham số ref / out cho dữ liệu đó hoặc bạn chỉ có thể trả về Tuple và có một chữ ký phương thức đẹp mà không cần phải tạo POCO mới.
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)
và thay vì phải sử dụng một tham số đầu ra, bạn lấy lại cả hai giá trị trong một bộ giá trị.
Tôi đã sử dụng một bộ tuple để giải quyết Vấn đề 11 của 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
}
}
Bây giờ tôi có thể giải quyết toàn bộ vấn đề với:
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);
}
}
Tôi có thể đã sử dụng một loại tùy chỉnh cho điều này, nhưng nó sẽ trông giống hệt như Tuple .
Cú pháp tuple của C # rất cồng kềnh, vì vậy các bộ giá trị rất khó khai báo. Và nó không có khớp mẫu, vì vậy chúng cũng khó sử dụng.
Nhưng đôi khi, bạn chỉ muốn một nhóm đối tượng đặc biệt mà không cần tạo một lớp cho nó. Ví dụ: giả sử tôi muốn tổng hợp một danh sách, nhưng tôi muốn hai giá trị thay vì một:
// 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));
Thay vì kết hợp một tập hợp các giá trị thành một kết quả, hãy mở rộng một kết quả thành một tập hợp các giá trị. Cách dễ nhất để viết hàm này là:
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
chuyển đổi một số trạng thái thành một tuple. Chúng tôi trả về giá trị đầu tiên từ bộ tuple và đặt trạng thái mới của chúng tôi thành giá trị thứ hai. Điều này cho phép chúng tôi duy trì trạng thái trong suốt quá trình tính toán.
Bạn sử dụng nó như vậy:
// 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
khá thẳng thắn, nhưng fibs
khôn khéo hơn một chút. Nó state
thực sự là một bộ tuple chứa fib (n-2) và fib (n-1) tương ứng.
new Tuple<Guid,string,...>
Tôi không thích việc lạm dụng chúng, vì chúng tạo ra mã không tự giải thích, nhưng chúng thật tuyệt vời khi triển khai các khóa ghép liên tục, vì chúng triển khai IStructuralEquatable và IStructuralComp so sánh được (để sử dụng cả để tra cứu và sắp xếp mục đích).
Và họ kết hợp tất cả các mã băm của mặt hàng của họ, trong nội bộ; ví dụ, đây là GetHashCode của Tuple (lấy từ ILSpy):
int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
{
return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
}
Tuples rất phù hợp để thực hiện nhiều hoạt động IO không đồng bộ cùng một lúc và trả về tất cả các giá trị cùng nhau. Dưới đây là các ví dụ về việc làm điều đó khi có và không có Tuple. Tuples thực sự có thể làm cho mã của bạn rõ ràng hơn!
Không có (làm tổ khó chịu!):
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);
}
});
}
});
}
});
Với Tuple
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);
}
});
Nếu bạn đang sử dụng một chức năng ẩn danh với một loại ngụ ý nào sau đó bạn không thực hiện các mã chưa rõ ràng bằng cách sử dụng các tuple. Đang khôi phục một Tuple từ một phương pháp? Sử dụng một cách tiết kiệm khi sự rõ ràng của mã là chìa khóa, theo ý kiến khiêm tốn của tôi. Tôi biết lập trình hàm trong C # rất khó để chống lại, nhưng chúng ta phải xem xét tất cả những lập trình viên C # "hướng đối tượng" cũ kỹ.
Tuples được sử dụng nhiều trong các ngôn ngữ chức năng có thể làm được nhiều việc hơn với chúng, giờ đây F # là ngôn ngữ .net 'chính thức' mà bạn có thể muốn tương tác với nó từ C # và chuyển chúng giữa các mã được viết bằng hai ngôn ngữ.
Tôi có xu hướng tránh Tuple
đối với hầu hết các tình huống vì nó ảnh hưởng đến khả năng đọc. Tuy nhiên,Tuple
rất hữu ích khi bạn cần nhóm dữ liệu không liên quan.
Ví dụ: giả sử bạn có một danh sách ô tô và các thành phố mà chúng đã được mua:
Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle
Bạn muốn tổng hợp số lượng cho mỗi ô tô mỗi thành phố:
Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]
Để làm điều này, bạn tạo một Dictionary
. Bạn có một vài lựa chọn:
Dictionary<string, Dictionary<string, int>>
.Dictionary<CarAndCity, int>
.Dictionary<Tuple<string, string>, int>
.Khả năng đọc bị mất với tùy chọn đầu tiên. Nó sẽ yêu cầu bạn viết nhiều mã hơn.
Tùy chọn thứ hai hoạt động và ngắn gọn, nhưng xe hơi và thành phố không thực sự liên quan và có thể không thuộc cùng một lớp.
Tùy chọn thứ ba là ngắn gọn và sạch sẽ. Đó là một cách sử dụng tốt Tuple
.
Một vài ví dụ ngoài ý muốn của tôi:
Ví dụ: bạn sẽ không muốn bao gồm System.Drawing trong một ứng dụng web chỉ để sử dụng Point / PointF và Size / SizeF.
Tuple
hữu ích trong các tình huống quan trọng Point
hoặc SomeVector
có thể hữu ích khi thực hiện các hoạt động đồ họa. Nó làm nổi bật hai giá trị rất ràng buộc với nhau. Tôi thường thấy các thuộc tính có tên StartTime và EndTime khi đọc mã, nhưng thậm chí còn tốt hơn cả ngày giờ và khoảng thời gian, Tuple buộc bạn phải xem xét cả hai giá trị mỗi khi bạn hoạt động trong lĩnh vực logic kinh doanh của mình. Hoặc trả về "hey, dữ liệu đã thay đổi (bool), đây là dữ liệu (loại khác)" được xử lý nặng thay vì đi qua các ràng buộc chuyên sâu.
Bạn nên cẩn thận với việc sử dụng Tuple
và có thể suy nghĩ kỹ trước khi làm điều này. Từ kinh nghiệm trước đây của tôi, tôi phát hiện ra rằng việc sử dụng Tuple
làm cho mã rất khó đọc và khó hỗ trợ trong tương lai. Một thời gian trước, tôi đã phải sửa một số mã mà các bộ giá trị được sử dụng hầu như ở khắp mọi nơi. Thay vì nghĩ về các mô hình đối tượng thích hợp, họ chỉ sử dụng các bộ giá trị. Đó là cơn ác mộng ... đôi khi tôi muốn giết người viết mã ...
Tôi không muốn nói rằng bạn không nên sử dụng Tuple
và nó xấu xa hay gì đó và tôi trăm phần trăm chắc chắn rằng có một số nhiệm vụ mà Tuple
ứng cử viên tốt nhất được sử dụng, nhưng có lẽ bạn nên nghĩ lại, bạn THỰC SỰ cần nó ?
Cách sử dụng tốt nhất cho Tuples mà tôi đã tìm thấy là khi cần trả về nhiều hơn 1 loại đối tượng từ một phương thức, bạn biết loại đối tượng và số lượng của chúng, và nó không phải là một danh sách dài.
Các lựa chọn thay thế đơn giản khác sẽ sử dụng tham số 'out'
private string MyMethod(out object)
hoặc làm từ điển
Dictionary<objectType1, objectType2>
Tuy nhiên, sử dụng Tuple sẽ tiết kiệm được việc tạo đối tượng 'out' hoặc về cơ bản phải tra cứu mục nhập trong từ điển;
Vừa tìm thấy giải pháp cho một trong những vấn đề của tôi ở Tuple. Nó giống như khai báo một lớp trong phạm vi của một phương thức, nhưng với khai báo lười biếng tên các trường của nó. Bạn thao tác với các bộ sưu tập các bộ giá trị, các thể hiện đơn lẻ của nó và sau đó tạo một bộ sưu tập kiểu ẩn danh với các tên trường bắt buộc, dựa trên bộ giá trị của bạn. Điều này tránh cho bạn tạo lớp mới cho mục đích này.
Nhiệm vụ là viết một phản hồi JSON từ LINQ mà không có bất kỳ lớp bổ sung nào:
//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);
Tất nhiên, chúng tôi có thể làm điều này bằng cách khai báo một Lớp mới cho các nhóm của tôi, nhưng ý tưởng tạo ra một bộ sưu tập nhất trí như vậy mà không cần khai báo các lớp mới.
Trong trường hợp của tôi, tôi đã phải sử dụng Tuple khi tôi phát hiện ra rằng chúng tôi không thể sử dụng tham số out trong một phương thức không đồng bộ. Đọc về nó ở đây . Tôi cũng cần một loại trả lại khác. Vì vậy, tôi đã sử dụng Tuple thay thế làm kiểu trả về của mình và đánh dấu phương thức là không đồng bộ.
Mẫu mã bên dưới.
...
...
// 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).
}
Đọc thêm về Tuple tại đây . Hi vọng điêu nay co ich.
Thay đổi hình dạng của các đối tượng khi bạn cần gửi chúng qua dây hoặc chuyển đến các lớp ứng dụng khác nhau và nhiều đối tượng được hợp nhất thành một:
Thí dụ:
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;
}
Tham số out là tuyệt vời khi chỉ có một vài giá trị cần được trả về, nhưng khi bạn bắt đầu gặp phải 4, 5, 6 hoặc nhiều giá trị cần được trả về, nó có thể trở nên khó sử dụng. Một tùy chọn khác để trả về nhiều giá trị là tạo và trả về một lớp / cấu trúc do người dùng xác định hoặc sử dụng Tuple để đóng gói tất cả các giá trị cần được trả về bởi một phương thức.
Tùy chọn đầu tiên, sử dụng một lớp / cấu trúc để trả về các giá trị, rất đơn giản. Chỉ cần tạo kiểu (trong ví dụ này là một cấu trúc) như sau:
public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}
Tùy chọn thứ hai, sử dụng Tuple, là một giải pháp thậm chí còn thanh lịch hơn so với sử dụng đối tượng do người dùng xác định. Một Tuple có thể được tạo để chứa bất kỳ số lượng giá trị nào thuộc các loại khác nhau. Ngoài ra, dữ liệu bạn lưu trữ trong Tuple là bất biến; khi bạn thêm dữ liệu vào Tuple thông qua phương thức khởi tạo hoặc phương thức Tạo tĩnh, dữ liệu đó không thể thay đổi được. Tuples có thể chấp nhận tối đa và bao gồm tám giá trị riêng biệt. Nếu bạn cần trả về nhiều hơn tám giá trị, bạn sẽ cần sử dụng lớp Tuple đặc biệt: Lớp Tuple Khi tạo một Tuple có nhiều hơn tám giá trị, bạn không thể sử dụng phương thức Tạo tĩnh — thay vào đó bạn phải sử dụng phương thức khởi tạo của lớp. Đây là cách bạn sẽ tạo một Tuple gồm 10 giá trị số nguyên:
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));
Tất nhiên, bạn có thể tiếp tục thêm nhiều Tuple vào cuối mỗi Tuple được nhúng, tạo ra bất kỳ kích thước Tuple nào mà bạn cần.
Tôi đã thử 3 cách để giải quyết vấn đề tương tự trong C # 7 và tôi đã tìm thấy một trường hợp sử dụng cho Tuples.
Làm việc với dữ liệu động trong các dự án web đôi khi có thể khó khăn khi lập bản đồ, v.v.
Tôi thích cách Tuple chỉ tự động ánh xạ vào item1, item2, itemN đối với tôi, điều này có vẻ mạnh mẽ hơn so với việc sử dụng các chỉ mục mảng nơi bạn có thể gặp phải một mục ngoài chỉ mục hoặc sử dụng kiểu ẩn danh nơi bạn có thể viết sai tên thuộc tính.
Có cảm giác như DTO đã được tạo miễn phí chỉ bằng cách sử dụng Tuple và tôi có thể truy cập tất cả các thuộc tính bằng itemN, cảm giác giống như nhập tĩnh hơn mà không cần phải tạo DTO riêng cho mục đích đó.
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 };
}
}
}
}