Làm thế nào để dễ dàng khởi tạo một danh sách các Tuples?


286

Tôi yêu tuples . Chúng cho phép bạn nhanh chóng nhóm các thông tin liên quan lại với nhau mà không cần phải viết một cấu trúc hoặc lớp cho nó. Điều này rất hữu ích trong khi tái cấu trúc mã rất cục bộ.

Tuy nhiên, việc khởi tạo một danh sách của chúng có vẻ hơi dư thừa.

var tupleList = new List<Tuple<int, string>>
{
    Tuple.Create( 1, "cow" ),
    Tuple.Create( 5, "chickens" ),
    Tuple.Create( 1, "airplane" )
};

Có cách nào tốt hơn không? Tôi rất thích một giải pháp dọc theo dòng của trình khởi tạo Từ điển .

Dictionary<int, string> students = new Dictionary<int, string>()
{
    { 111, "bleh" },
    { 112, "bloeh" },
    { 113, "blah" }
};

Chúng ta không thể sử dụng một cú pháp tương tự?


2
Trong trường hợp này tại sao bạn không sử dụng từ điển thay vì danh sách các Tuples?
Ed S.

17
@Ed S.: A Dictionarykhông cho phép các khóa trùng lặp.
Steven Jeuris

2
@EdS.: Mỗi lần nó không phải là hai tuple trong đó một mặt hàng có thể băm / có thể đặt hàng và duy nhất.

Điểm tốt, đã không nhận thấy khóa trùng lặp.
Ed S.

Câu trả lời:


279

c # 7.0 cho phép bạn làm điều này:

  var tupleList = new List<(int, string)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

Nếu bạn không cần một List, nhưng chỉ cần một mảng, bạn có thể làm:

  var tupleList = new(int, string)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

Và nếu bạn không thích "Item1" và "Item2", bạn có thể làm:

  var tupleList = new List<(int Index, string Name)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

hoặc cho một mảng:

  var tupleList = new (int Index, string Name)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

cho phép bạn làm: tupleList[0].IndextupleList[0].Name

Khung 4.6.2 trở xuống

Bạn phải cài đặt System.ValueTupletừ Trình quản lý gói Nuget.

Khung 4.7 trở lên

Nó được xây dựng vào khuôn khổ. Đừng không cài đặt System.ValueTuple. Trong thực tế, loại bỏ nó xóa nó khỏi thư mục bin.

lưu ý: Trong cuộc sống thực, tôi sẽ không thể chọn giữa bò, gà hoặc máy bay. Tôi sẽ thực sự bị rách


2
Điều này có thể được sử dụng trên lõi .net 2.0 không?
Hôm nay,

2
@ ĐẠI LÝ, System.ValueTuplehỗ trợ lõi 2.0. Nhưng, hãy thử nó mà không có Nuget trước, vì các gói không cần thiết có thể gây ra vấn đề. Vì vậy, bằng cách này hay cách khác, có. Sử dụng c # v7 hoặc cao hơn nếu có thể.
chập chững

1
Câu trả lời hoàn toàn hoàn hảo! Cảm ơn!
Vitox

224

Đúng! Điều này là có thể .

Các {} Cú pháp của bộ sưu tập initializer hoạt động trên bất kỳ IEnumerable loại trong đó có một Add phương pháp với đúng số lượng đối số. Không cần bận tâm đến cách thức hoạt động của trang bìa, điều đó có nghĩa là bạn chỉ cần mở rộng từ Danh sách <T> , thêm phương thức Thêm tùy chỉnh để khởi tạo T của bạn và bạn đã hoàn tất!

public class TupleList<T1, T2> : List<Tuple<T1, T2>>
{
    public void Add( T1 item, T2 item2 )
    {
        Add( new Tuple<T1, T2>( item, item2 ) );
    }
}

Điều này cho phép bạn làm như sau:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};

39
Một nhược điểm là phải viết lớp. Giống như bạn, tôi yêu các bộ dữ liệu vì tôi không phải viết một lớp hoặc struct.
tạm biệt

2
@BillW Không quá chắc chắn đây là bài viết chính xác, ... nhưng tôi biết Jon Skeet đã viết về nó trước đây .
Steven Jeuris

1
điều này làm việc groceryList[0] == groceryList[1]hoặc một so sánh phải được thực hiện?
Byyo

Tôi đã tạo Gói Nuget với các phương thức Thêm trên ICollection để tiết kiệm cho người khác thời gian phải duy trì mã này. Xem ở đây để biết gói: nuget.org/packages/Naos.Recipes.TupleInitialulators Xem tại đây để biết mã: github.com/NaosProject/Naos.Recipes/blob/master/ trộm Điều này sẽ bao gồm tệp cs trong giải pháp của bạn trong ".Naos Thư mục .Recipes ", do đó bạn không phải kéo theo phụ thuộc lắp ráp
SFun28

83

C # 6 thêm một tính năng mới chỉ dành cho điều này: tiện ích mở rộng Thêm phương thức. Điều này luôn có thể có đối với VB.net nhưng hiện đã có trong C #.

Bây giờ bạn không phải thêm Add()trực tiếp các phương thức vào các lớp của mình, bạn có thể triển khai chúng như các phương thức mở rộng. Khi mở rộng bất kỳ loại vô số nào bằng một Add()phương thức, bạn sẽ có thể sử dụng nó trong các biểu thức khởi tạo bộ sưu tập. Vì vậy, bạn không phải xuất phát từ danh sách một cách rõ ràng nữa ( như đã đề cập trong câu trả lời khác ), bạn chỉ cần mở rộng nó.

public static class TupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
            T1 item1, T2 item2)
    {
        list.Add(Tuple.Create(item1, item2));
    }

    public static void Add<T1, T2, T3>(this IList<Tuple<T1, T2, T3>> list,
            T1 item1, T2 item2, T3 item3)
    {
        list.Add(Tuple.Create(item1, item2, item3));
    }

    // and so on...
}

Điều này sẽ cho phép bạn làm điều này trên bất kỳ lớp nào thực hiện IList<>:

var numbers = new List<Tuple<int, string>>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
    { 4, "four" },
    { 5, "five" },
};
var points = new ObservableCollection<Tuple<double, double, double>>
{
    { 0, 0, 0 },
    { 1, 2, 3 },
    { -4, -2, 42 },
};

Tất nhiên, bạn không bị hạn chế mở rộng các bộ sưu tập các bộ dữ liệu, nó có thể dành cho các bộ sưu tập thuộc bất kỳ loại cụ thể nào bạn muốn cú pháp đặc biệt.

public static class BigIntegerListExtensions
{
    public static void Add(this IList<BigInteger> list,
        params byte[] value)
    {
        list.Add(new BigInteger(value));
    }

    public static void Add(this IList<BigInteger> list,
        string value)
    {
        list.Add(BigInteger.Parse(value));
    }
}

var bigNumbers = new List<BigInteger>
{
    new BigInteger(1), // constructor BigInteger(int)
    2222222222L,       // implicit operator BigInteger(long)
    3333333333UL,      // implicit operator BigInteger(ulong)
    { 4, 4, 4, 4, 4, 4, 4, 4 },               // extension Add(byte[])
    "55555555555555555555555555555555555555", // extension Add(string)
};

C # 7 sẽ được thêm hỗ trợ cho các bộ dữ liệu được tích hợp vào ngôn ngữ, mặc dù chúng sẽ thuộc loại khác ( System.ValueTuplethay vào đó). Vì vậy, sẽ rất tốt nếu thêm quá tải cho các bộ giá trị để bạn có tùy chọn sử dụng chúng. Thật không may, không có chuyển đổi ngầm định nghĩa giữa hai.

public static class ValueTupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
        ValueTuple<T1, T2> item) => list.Add(item.ToTuple());
}

Bằng cách này, việc khởi tạo danh sách sẽ trông đẹp hơn nữa.

var points = new List<Tuple<int, int, int>>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Nhưng thay vì trải qua tất cả những rắc rối này, có lẽ tốt hơn là chuyển sang sử dụng ValueTupleđộc quyền.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Cuối cùng tôi đã xem xét kỹ điều này trong khi cập nhật thư viện của mình lên C # 6.0. Mặc dù nó có vẻ tốt trong nháy mắt, tôi thích giải pháp được đăng trước đây của tôi hơn vì tôi thấy TupleList<int, string>.dễ đọc hơn List<Tuple<int, string>>.
Steven Jeuris

1
Đó là điều mà một bí danh loại có thể khắc phục. Cấp bí danh không thể là chung chung mà có thể thích hợp hơn. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado

Tôi đã tạo Gói Nuget với các phương thức Thêm trên ICollection để tiết kiệm cho người khác thời gian phải duy trì mã này. Xem ở đây để biết gói: nuget.org/packages/Naos.Recipes.TupleInitialulators Xem tại đây để biết mã: github.com/NaosProject/Naos.Recipes/blob/master/ trộm Điều này sẽ bao gồm tệp cs trong giải pháp của bạn trong ".Naos Thư mục .Recipes ", do đó bạn không phải kéo theo phụ thuộc lắp ráp
SFun28

42

Bạn có thể làm điều này bằng cách gọi hàm tạo mỗi lần tốt hơn một chút

var tupleList = new List<Tuple<int, string>>
{
    new Tuple<int, string>(1, "cow" ),
    new Tuple<int, string>( 5, "chickens" ),
    new Tuple<int, string>( 1, "airplane" )
};

6
Tôi không thể làm cho mã gốc hoạt động được, vì vậy tôi đã sửa đổi nó thành mã mà tôi nghĩ nó nên ... điều này có thể đảo ngược ý kiến ​​của bạn về việc liệu cú pháp "tốt hơn một chút" sau tất cả :)
ngày

5
ít nhất là sử dụng Tuple.Createthay thế và bạn có thể suy ra các đối số kiểu
Dave Cousineau

28

Câu hỏi cũ, nhưng đây là những gì tôi thường làm để làm cho mọi thứ dễ đọc hơn một chút:

Func<int, string, Tuple<int, string>> tc = Tuple.Create;

var tupleList = new List<Tuple<int, string>>
{
    tc( 1, "cow" ),
    tc( 5, "chickens" ),
    tc( 1, "airplane" )
};

Hoạt động trên các phiên bản cũ của .NET, quá!
Ed Bayiates

1

Super Duper Cũ Tôi biết nhưng tôi sẽ thêm phần của mình vào việc sử dụng Linq và lambdas tiếp tục trên các phương thức bằng cách sử dụng C # 7. Tôi cố gắng sử dụng các bộ dữ liệu có tên thay thế cho DTO và các phép chiếu ẩn danh khi được sử dụng lại trong một lớp. Có để chế giễu và kiểm tra bạn vẫn cần các lớp nhưng thực hiện mọi thứ nội tuyến và chuyển xung quanh trong một lớp là tốt để có tùy chọn IMHO mới hơn này. Bạn có thể khởi tạo chúng từ

  1. Khởi tạo trực tiếp
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Tắt một bộ sưu tập hiện có, và bây giờ bạn có thể trả lại các bộ dữ liệu được gõ tốt tương tự như cách các phép chiếu ẩn danh được sử dụng để thực hiện.
public class Hold
{
    public int Id { get; set; }
    public string Name { get; set; }
}

//In some method or main console app:
var holds = new List<Hold> { new Hold { Id = 1, Name = "Me" }, new Hold { Id = 2, Name = "You" } };
var anonymousProjections = holds.Select(x => new { SomeNewId = x.Id, SomeNewName = x.Name });
var namedTuples = holds.Select(x => (TupleId: x.Id, TupleName: x.Name));
  1. Sử dụng lại các bộ dữ liệu sau này với các phương thức nhóm hoặc sử dụng một phương thức để xây dựng chúng theo dòng trong logic khác:
//Assuming holder class above making 'holds' object
public (int Id, string Name) ReturnNamedTuple(int id, string name) => (id, name);
public static List<(int Id, string Name)> ReturnNamedTuplesFromHolder(List<Hold> holds) => holds.Select(x => (x.Id, x.Name)).ToList();
public static void DoSomethingWithNamedTuplesInput(List<(int id, string name)> inputs) => inputs.ForEach(x => Console.WriteLine($"Doing work with {x.id} for {x.name}"));

var namedTuples2 = holds.Select(x => ReturnNamedTuple(x.Id, x.Name));
var namedTuples3 = ReturnNamedTuplesFromHolder(holds);
DoSomethingWithNamedTuplesInput(namedTuples.ToList());

Bạn làm tôi cảm thấy già. :) Tôi có thể đang thiếu một cái gì đó ở đây, nhưng việc khởi tạo 'giữ' hoàn toàn không súc tích. Đây là điểm chính xác của câu hỏi.
Steven Jeuris

@Steven Jeuris Không, bạn đã đúng Tôi đã sao chép và dán sai đoạn mã cho Điểm 1. Cần phải đi chậm hơn một chút trước khi tôi nhấn 'gửi'.
djangojazz

0

Tại sao lại thích tuples? Nó giống như các loại ẩn danh: không có tên. Không thể hiểu cấu trúc của dữ liệu.

Tôi thích những lớp học cổ điển

class FoodItem
{
     public int Position { get; set; }
     public string Name { get; set; }
}

List<FoodItem> list = new List<FoodItem>
{
     new FoodItem { Position = 1, Name = "apple" },
     new FoodItem { Position = 2, Name = "kiwi" }
};

3
Như tôi đã nói: "Họ cho phép bạn nhanh chóng nhóm các thông tin liên quan lại với nhau mà không cần phải viết một cấu trúc hoặc lớp cho nó." Trong trường hợp lớp này chỉ được sử dụng trong một phương thức duy nhất như một cấu trúc dữ liệu của trình trợ giúp, tôi thích sử dụng Tuples hơn, vì vậy không nên điền vào một không gian tên với một lớp không cần thiết.
Steven Jeuris

1
Bạn đang nghĩ về lớp Tuple cũ hơn (không phải cấu trúc). C # 7 đã thêm các bộ dữ liệu dựa trên cấu trúc (được hỗ trợ bởi loại ValueTuple mới) có thể có tên, vì vậy bạn có thể hiểu cấu trúc dữ liệu
Steve Niles

0

Một kỹ thuật tôi nghĩ là dễ dàng hơn một chút và chưa được đề cập trước đây:

var asdf = new [] { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
}.ToList();

Tôi nghĩ rằng đó là một chút sạch sẽ hơn:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};

Điều đó xuất hiện nhiều hơn để nếm thử xem bạn có muốn đề cập đến các loại rõ ràng hay không, trong cùng một cách vô ích như sử dụng varhay không. Cá nhân tôi thích gõ rõ ràng (khi nó không được sao chép trong ví dụ: hàm tạo).
Steven Jeuris

-4
    var colors = new[]
    {
        new { value = Color.White, name = "White" },
        new { value = Color.Silver, name = "Silver" },
        new { value = Color.Gray, name = "Gray" },
        new { value = Color.Black, name = "Black" },
        new { value = Color.Red, name = "Red" },
        new { value = Color.Maroon, name = "Maroon" },
        new { value = Color.Yellow, name = "Yellow" },
        new { value = Color.Olive, name = "Olive" },
        new { value = Color.Lime, name = "Lime" },
        new { value = Color.Green, name = "Green" },
        new { value = Color.Aqua, name = "Aqua" },
        new { value = Color.Teal, name = "Teal" },
        new { value = Color.Blue, name = "Blue" },
        new { value = Color.Navy, name = "Navy" },
        new { value = Color.Pink, name = "Pink" },
        new { value = Color.Fuchsia, name = "Fuchsia" },
        new { value = Color.Purple, name = "Purple" }
    };
    foreach (var color in colors)
    {
        stackLayout.Children.Add(
            new Label
            {
                Text = color.name,
                TextColor = color.value,
            });
        FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
    }

this is a Tuple<Color, string>

Làm thế nào để bạn có được cú pháp giá trị / tên với một Tuple?
Andreas Reiff

Trình biên dịch nói rằng loại ẩn danh không hoàn toàn có thể chuyển đổi thành Tuple
cthepenier
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.