我想在 C# 中生成一个列表。我缺少 python 的列表理解。是否有一种 C# 方法可以像 Python 中的列表推导或生成器表达式那样动态创建集合?
5 回答
如果您使用的是 C# 3.0 (VS2008),那么 LINQ to Objects 可以做非常相似的事情:
List<Foo> fooList = new List<Foo>();
IEnumerable<Foo> extract = from foo in fooList where foo.Bar > 10 select Foo.Name.ToUpper();
Matt 提到了查询表达式。顺便说一下,这些通常可用于 LINQ - 而不仅仅是 LINQ to Objects。(例如,应用于 LINQ to SQL 数据上下文的相同查询将在数据库上执行过滤器和投影。)
C# 3 中的查询表达式只是编写普通 C# 代码的语法糖——尽管查询表达式通常最终调用扩展方法。(它们不必这样做,编译器也不关心,但它们通常会这样做。)您可以对集合执行各种操作,这些操作在 C# 查询表达式中不可用,但受方法调用支持,因此值得了解这两种语法。例如,Matt 的查询表达式为:
List<Foo> fooList = new List<Foo>();
IEnumerable<string> extract = from foo in fooList where foo.Bar > 10 select foo.Name.ToUpper();
被“预处理”成:
List<Foo> fooList = new List<Foo>();
IEnumerable<string> extract = fooList.Where(foo => foo.Bar > 10)
.Select(foo => foo.Name.ToUpper());
如果您想(比如说)根据原始集合中值的索引进行过滤,您可以使用Where 的适当重载,该重载通过查询表达式不可用:
List<Foo> fooList = new List<Foo>();
IEnumerable<string> extract = fooList.Where((foo, index) => foo.Bar > 10 + index)
.Select(foo => foo.Name.ToUpper());
或者您可以找到符合条件的最长名称的长度:
List<Foo> fooList = new List<Foo>();
int longestName = fooList.Where((foo, index) => foo.Bar > 10 + index)
.Select(foo => foo.Name)
.Max();
(您不必在单独的方法中进行投影和最大值 - 有一个重载Max
也需要一个投影。)
我的观点是,使用扩展方法,您可以非常轻松地构建复杂的查询。
您还提到了 Python 生成器 - C# 以迭代器块的形式具有此功能。事实上,这些在实现类似 LINQ 的运算符时非常有用。(因为大多数 LINQ to Objects 都基于扩展方法,所以您可以将自己的运算符添加到 LINQ 中,这些运算符看起来“本机” - 尽管您自己不能更改查询表达式语法。)
List<T>.ConvertAll
通过对现有列表中的每个项目执行相同的操作,然后返回一个新集合,其行为就像列表推导式一样。这是使用 Linq 的替代方法,尤其是在您仍在使用 .NET 2.0 时。
在 Python 中,一个简单的列表理解示例:
>>> foo = [1, 2, 3]
>>> bar = [x * 2 for x in foo]
>>> bar
[2, 4, 6]
对于 C# 3.0,您可以传递一个 lambda 函数来指定所需的映射函数类型。
public static void Main()
{
var foo = new List<int>{ 1, 2, 3};
var bar = foo.ConvertAll(x => x * 2); // list comprehension
foreach (var x in bar)
{
Console.WriteLine(x); // should print 2 4 6
}
}
对于 C# 2.0,您可以使用带有Converter
委托的匿名方法来执行等效操作。
public static void Main()
{
List<int> foo = new List<int>(new int[]{ 1, 2, 3});
List<int> bar = foo.ConvertAll(new Converter<int, int>(delegate(int x){ return x * 2; })); // list comprehension
foreach (int x in bar)
{
Console.WriteLine(x); // should print 2 4 6
}
}
(注意:同样可以使用 Arrays 完成Array.ConvertAll
我知道这是一个非常晚的答案,但我也想知道 C# 是否有任何相当于 python 的列表理解的东西。据我所知, Matt Campbell和Jon Skeet的回答展示了如何从现有列表中提取列表。但是 python 的列表推导也可以从头开始创建列表。所以这就是我想出的。
首先,我将展示 python 列表理解,然后展示我想出的 C# 等价物。
玩具任务是创建一个这样的字符串
cb01, cb02, cb02, ... , cb50
Python列表理解:
s = ', '.join('cb{0:02}'.format(i+1) for i in range(50))
我想出的 C# 等价物:
string s = String.Join(", ", new Func<List<string>>( () =>
{
List<string> list = new List<string>();
foreach (int i in Enumerable.Range(1, 50))
list.Add(String.Format("cb{0:00}", i));
return list;
}).Invoke());
我不确定我是否做过头。
编辑:(从 Jon Skeet 在他的评论中提到的,一个真正的单行相当于 python 的列表理解)
String.Join(", ", Enumerable.Range(1, 50).Select(x => $"cb{x:00}")))
请注意,这$
件事是C# 6 的功能。如果您仍然不使用 C# 6,则可以使用旧String.Format()
方法。
有这个:
new List<FooBar> { new Foo(), new Bar() }
它只比它的 python 等价物长一点:
[Foo(), Bar()]
然后是这样的:
public IEnumerable<FooBar> myFooBarGenerator() {
yield return new Foo();
yield return new Bar();
}
这相当于python:
def myFooBarGenerator():
yield Foo()
yield Bar()