如何轻松地初始化元组列表?


286

我爱元组。它们使您可以快速将相关信息分组在一起,而不必为其编写结构或类。在重构非常本地化的代码时,这非常有用。

但是,初始化它们的列表似乎有点多余。

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

有没有更好的办法?我希望有一个类似于Dictionary Initializer的解决方案。

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

我们不能使用类似的语法吗?


2
在这种情况下,为什么使用字典而不是元组列表?
Ed S.

17
@Ed S .: A Dictionary不允许重复的键。
Steven Jeuris 2011年

2
@EdS .:每次不是两个元组,其中一项是可哈希/可排序且唯一的。

好点,没有注意到重复的密钥。
Ed S.

Answers:


279

c#7.0允许您执行以下操作:

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

如果您不需要List,而只需要一个数组,则可以执行以下操作:

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

如果您不喜欢“ Item1”和“ Item2”,则可以执行以下操作:

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

或数组:

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

这可以让您做到:tupleList[0].IndextupleList[0].Name

框架4.6.2及以下

您必须System.ValueTuple从Nuget软件包管理器进行安装。

框架4.7及更高版本

它内置在框架中。不要安装System.ValueTuple。事实上,将其删除,并从bin目录中删除。

注意:在现实生活中,我将无法在牛,鸡或飞机之间进行选择。我真的会被撕成碎片。


2
可以在.net core 2.0上使用吗?
АлексаЈевтић

2
@АлексаЈевтић,System.ValueTuple支持核心2.0。但是,请先使用Nuget尝试一下,因为不必要的软件包可能会引起问题。因此,是的。如果可能,请使用c#v7或更高版本。
toddmo '18 -4-5

1
绝对完美的答案!谢谢!
Vitox

224

是! 这是可能的

集合初始值设定项的{}语法可 用于具有Add方法(带有正确数量的参数)的任何IEnumerable类型。无需理会它的幕后工作原理,这意味着您可以从List <T>扩展,添加自定义的Add方法来初始化 T,您就完成了!

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

这使您可以执行以下操作:

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

39
缺点是必须编写此类。像您一样,我喜欢元组,因为我不必编写类或结构。
12

2
@BillW不太确定这是确切的帖子,... 但是我知道Jon Skeet之前已经写过有关它的文章
史蒂文·杰里斯

1
这项工作groceryList[0] == groceryList[1]还是必须进行比较?
Byyo

我在ICollection上使用Add方法创建了一个Nuget包,以节省其他人维护该代码的时间。请参阅此处的软件包:nuget.org/packages/Naos.Recipes.TupleInitializers请参见此处的代码:github.com/NaosProject/Naos.Recipes/blob/master/… 这将在解决方案中的“ .Naos”下包含一个CS文件。 “ .Recipes”文件夹中,因此您无需在程序集依赖项中四处拖动
SFun28

83

C#6为此添加了一个新功能:扩展方法Add。对于VB.net来说,这始终是可能的,但现在在C#中可用。

现在,您不必Add()直接向类中添加方法,您可以将它们实现为扩展方法。当使用Add()方法扩展任何可枚举类型时,您将可以在集合初始值设定项表达式中使用它。因此,您不必再从列表中显式派生(如另一个答案中所述),只需扩展它即可。

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...
}

这将允许您在实现以下任何类的基础上执行此操作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 },
};

当然,您不限于扩展元组的集合,它可以用于您想要特殊语法的任何特定类型的集合。

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将增加对内置在该语言中的元组的支持,尽管它们将是另一种类型(System.ValueTuple相反)。因此,最好为值元组添加重载,以便您也可以选择使用它们。不幸的是,两者之间没有定义隐式转换。

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

这样,列表初始化看起来会更好。

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

但是,与其解决所有这些麻烦,不如转而使用ValueTuple专有。

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

在将库更新为C#6.0时,我终于对此进行了体面的研究。尽管乍一看看起来不错,但我更喜欢以前发布的解决方案,因为我发现TupleList<int, string>.它比更具可读性List<Tuple<int, string>>
史蒂文·杰里斯

1
这是类型别名可以解决的问题。授予别名本身不能是通用的,这可能是更好的选择。using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
杰夫·梅卡多

我在ICollection上使用Add方法创建了一个Nuget包,以节省其他人维护该代码的时间。请参阅此处的软件包:nuget.org/packages/Naos.Recipes.TupleInitializers请参见此处的代码:github.com/NaosProject/Naos.Recipes/blob/master/…这将在解决方案中的“ .Naos”下包含一个CS文件。 “ .Recipes”文件夹中,因此您无需在程序集依赖项中四处拖动
SFun28,2013年

42

您可以通过每次调用构造函数来实现

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
我无法使原始代码正常工作,因此我已将其修改为我认为应该的样子……这可能会反过来使您对语法是否“稍微好一些”的看法完全相反:)
一天

5
至少使用Tuple.Create代替,您就可以推断出类型实

28

老问题了,但这是我通常要做的,以使事情更具可读性:

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" )
};

也可以在旧版本的.NET上运行!
Ed Bayiates

1

Super Duper Old我知道,但是我会在使用C#7的方法上添加有关使用Linq和lambda的延续性文章。在类中重用时,我尝试使用命名元组代替DTO和匿名投影。是的,对于模拟和测试,您仍然需要类,但是使用此更新的选项恕我直言,在类中进行内联和传递很高兴。您可以从实例化它们

  1. 直接实例化
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. 在现有集合的基础上,现在您可以返回类型正确的元组,类似于过去完成匿名投影的方式。
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. 稍后使用分组方法重用元组,或使用一种方法以其他逻辑内联地构造它们:
//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());

你让我觉得老。:)我在这里可能会遗漏一些东西,但是初始化“ holds”并不十分简洁。这是问题的确切重点。
史蒂文·杰里斯

@Steven Jeuris不,你是对的,我复制并粘贴了Point 1的错误代码。在我点击“提交”之前,需要慢一点。
djangojazz

0

为什么喜欢元组?就像匿名类型:没有名字。无法理解数据结构。

我喜欢经典课程

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
正如我所说:“它们使您可以快速将相关信息分组在一起,而不必为其编写结构或类。” 如果此类仅在单个方法中用作辅助数据结构,则我更喜欢使用Tuples,这样就不会在名称空间中填充不必要的类。
史蒂文·杰里斯

1
您正在考虑旧的Tuple类(而不是struct)。C#7添加了可以具有名称的基于结构的元组(由新的ValueTuple类型支持),因此您可以了解数据的结构
Steve Niles

0

我认为一种技术稍微容易一些,并且在此之前没有提到:

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

我认为这比以下方法更干净:

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

无论您是否愿意显式地提及类型,无论是否使用,都无济于事var。我个人更喜欢显式键入(当它在例如构造函数中不重复时)。
史蒂文·杰里斯

-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>

如何使用元组获取值/名称语法?
Andreas Reiff 2015年

编译器告知匿名类型不能隐式转换为Tuple
Cthepenier
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.