创建一个匿名类型的通用列表有什么技巧吗?

41

有时我需要使用元组,比如我有一些坦克和它们的目标坦克(它们会追逐目标或类似的操作):

List<Tuple<Tank,Tank>> mylist = new List<Tuple<Tank,Tank>>();

然后当我遍历列表时,我通过以下方式访问它们

mylist[i].item1 ...
mylist[i].item2 ...

很混乱,我总是忘记item1和item2分别是什么,如果我能通过以下方式访问它们:

mylist[i].AttackingTank...
mylist[i].TargetTank...

如果不定义一个类,有没有一种更清晰的方法实现它呢?

MyTuple
{
public Tank AttackingTank;
public Tank TargetTank;
}

我希望避免定义这个类,因为在不同情况下我将不得不定义许多不同的类,我能否使用一些“技巧”使其匿名化。

例如:

var k = new {Name = "me", phone = 123};
mylist.Add(k);
当然问题的关键在于我没有类型可以传递给定义列表时。

1
你尝试过使用动态关键字来声明列表类型吗? - Rob G
1
定义类有什么问题吗?看起来是显而易见的解决方案。至于需要“定义许多不同的类”,也许有一个继承结构可以利用? - Matt Burland
2
可能是A generic list of anonymous class的重复问题。 - nawfal
9个回答

99

你可以创建一个匿名类型的空列表,然后使用它,享受完整的智能感知和编译时检查:

var list = Enumerable.Empty<object>()
             .Select(r => new {A = 0, B = 0}) // prototype of anonymous type
             .ToList();

list.Add(new { A = 4, B = 5 }); // adding actual values

Console.Write(list[0].A);

1
有一个方便的选择器,可以将对象转换为匿名类型,因此我们可以获得匿名类型列表,而不是对象列表。这个示例编译得很好。 - alex
因为这个回答基本上和我的回答一样,而且这个回答比我的早,所以我点了赞。 - Jeppe Stig Nielsen
(在我编辑了我的答案并包含了另一种技术后,我的答案不再是“大致相同”的。) - Jeppe Stig Nielsen
这太棒了! - Dan Bechard
另一种解决方案是创建匿名类型的通用列表。请查看此链接 - Shaiju T
显示剩余2条评论

18
你可以使用一个 List<dynamic>
 var myList = new List<dynamic>();
 myList.Add(new {Tank = new Tank(), AttackingTank = new Tank()});

 Console.WriteLine("AttackingTank: {0}", myList[0].AttackingTank);

尽管这样可以让您添加项目,但无法将引用分配给匿名列表。 - Dave Cousineau

8
值得注意的是,现在可以使用这种语法。它在C# 7.0中被引入。
var tanks = new List<(Tank AttackingTank, Tank TargetTank)>();

(Tank, Tank) tTank = (new Tank(), new Tank());
tanks.Add(tTank);

var a = tanks[0].AttackingTank;
var b = tanks[0].TargetTank;

1
我喜欢这个并在我的应用程序中使用它,但是这种语法叫什么?我想对它进行一些额外的研究。 - Mike Barlow - BarDev
@MikeBarlow-BarDev 微软博客将其称为元组类型和元组字面量 https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/ - Puchacz

8

以下是一个技巧:

var myList = Enumerable.Empty<int>()
    .Select(dummy => new { AttackingTank = default(Tank), TargetTank = default(Tank), })
    .ToList();

如果 Tank 是一个类类型,你可以写成 (Tank)null 而不是 default(Tank)。你也可以使用一些你手头上已经有的 Tank 实例。
var myList = Enumerable.Repeat(
    new { AttackingTank = default(Tank), TargetTank = default(Tank), },
    0).ToList();

如果你创建一个通用方法,就不需要使用Enumerable.Empty。可以这样实现:
static List<TAnon> GetEmptyListWithAnonType<TAnon>(TAnon dummyParameter)
{
    return new List<TAnon>();
}

这个函数被称为“TAnon”,当然,这是根据使用情况推断出来的,如下所示:

var myList = GetEmptyListWithAnonType(new { AttackingTank = default(Tank), TargetTank = default(Tank), });

5
你可以编写一个通用方法,接受 params 参数并返回它:
public static IList<T> ToAnonymousList<T>(params T[] items)
{
  return items;
}

现在你可以这样说:

var list=ToAnonymousList
(
  new{A=1, B=2},
  new{A=2, B=2}
);

我想在一个方法中返回列表变量。该列表是IList<T>,但T是未知的。 - TheBoubou

2
这个怎么样?ExpandoObject
dynamic tuple = new ExpandoObject(); 
tuple.WhatEverYouWantTypeOfTank = new Tank(); // Value of any type

修改:
dynamic tuple = new ExpandoObject();
tuple.AttackingTank = new Tank();
tuple.TargetTank = new Tank();

var mylist = new List<dynamic> { tuple };

//access to items
Console.WriteLine(mylist[0].AttackingTank);

你的回答只是解决了动态创建类型以替换Tuple的问题,但实际上OP并没有遇到这个问题。他们需要知道如何处理泛型列表中的动态类型。在你的回答中解决这个问题,你就可以继续(当然,我已经回答过了 ;))。 - moribvndvs
@HackedByChinese,嗯,我加了一些编辑...而且我不确定你的代码是否会起作用...这种动态对象初始化(new{ Tank = new Tank(), AttackingTank = new Tank() })是不支持的。 - Dmytro
dynamic 与匿名类型一起使用时效果很好,只要您遵守匿名类型的一般限制即可。例如:如果 OP 将生成的列表作为方法的一部分返回,则可能会出现从其他程序集中使用该结果的问题(因为匿名类型是内部的,我认为)。在这种情况下,您需要使用 ExpandoObject,但此时它变得无意义,因为 OP 应该使用适当的元组类来更好地表达 API。 - moribvndvs

1
甚至更简单
        var tupleList = (
           new[] {
                new { fruit = "Tomato", colour = "red"},
                new { fruit = "Tomato", colour = "yellow"},
                new { fruit = "Apple", colour = "red"},
                new { fruit = "Apple", colour = "green"},
                new { fruit = "Medlar", colour = "russet"}
            }).ToList();

失去静态函数的功能。


1

在这里再补充一个有用的小技巧。我偶尔使用Alex的答案,但是当我需要它时,追踪它会让我有些疯狂,因为它不明显(我发现自己在搜索“new {”)。

所以我添加了以下小静态方法(我希望我能将其作为扩展方法,但是什么类型呢?):

public static List<T> CreateEmptyListOf<T>(Func<T> itemCreator)
{
    return Enumerable
        .Empty<object>()
        .Select(o => itemCreator())
        .ToList();
}

这样做可以将每次需要使用该模式的不同部分与相同部分隔离开来。我像这样调用它:
var emptyList = Ext.CreateEmptyListOf(() => new { Name = default(string), SomeInt = default(int) });

1
关于你的问题:你可以用 return new List<T>(); 替换方法体。现在你不再需要 Func<T>,只需要 T,所以你可以将参数定义更改为 this T itemSample。嘿,这是任何类型的扩展方法。 - springy76
是的,那也可以。它确实有一个(可能不重要的)副作用:您必须创建一个实例才能创建列表。这两个在代码中都有点混淆。使用您的方法将如下所示:var emptyList = new { Name = default(string), SomeInt = default(int) }.CreateEmptyListOf();,我不喜欢;它看起来像我正在创建东西,但实际上并没有。但那只是我的意见。真正最好的情况是如果有内置的扩展或运算符。 - rrreee
两种代码都在堆上创建一个对象:委托或匿名存根对象。最好使用真正的类,并将匿名类的使用限制为隐式或显式的LINQ链。 - springy76

0

你可以创建一个对象列表:

List<object> myList = new List<object>();

然后将匿名类型添加到您的列表中:

myList.Add(new {Name = "me", phone = 123});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接