CodeGym /课程 /C# SELF /用 Select做数据投影

用 Select做数据投影

C# SELF
第 31 级, 课程 3
可用

1. 入门

你追剧的时候,不会总是知道所有角色的全部信息,只知道剧情需要的那点。在编程里也经常这样:我们不需要整个对象,只要它的某些字段、值,甚至是基于它们算出来的东西。

投影在LINQ里,就是把原始序列的元素变成新样子。通常用Select方法。你可以把Select想象成一个魔法机器,它拿到每个元素,套用一个函数,然后把结果吐出来。就这么简单,没啥花活。

什么时候用投影?

  • 只想显示用户的名字,不要所有数据。
  • 做邮件群发:从客户对象里只拿地址和名字。
  • 算东西:给商品价格加上税,得到新集合。
  • UI用:只展示部分数据,界面不至于太乱。

2. Select方法:语法和基础用法

基本语法

LINQ支持两种风格:方法式(Method Syntax)和查询式(Query Syntax)。用Select两种写法结果都一样。

方法式写法

var query = myCollection.Select(x => x.要返回什么);

这里x是集合里的每个元素,右边是把元素变成你想要的表达式。

查询式写法

var query = from x in myCollection
            select x.要返回什么;

这里写法有点像SQL。

简单例子:数字列表

var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 我想得到它们的平方
var squares = numbers.Select(n => n * n);

foreach (var s in squares)
{
    Console.WriteLine(s); // 1, 4, 9, 16, 25
}

我们刚刚把数字集合变成了它们的平方集合——一下子搞定,不用手写循环!

例子2

之前我们有个Product类:

public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

假设我们有个列表:

var products = new List<Product>
{
    new Product { Name = "巧克力", Price = 120 },
    new Product { Name = "奶酪", Price = 250 },
    new Product { Name = "面包", Price = 55 }
};

如果我们只想拿到所有商品的名字,其实不需要整个对象对吧?只要名字列表就行:

var names = products.Select(p => p.Name);
foreach (var name in names)
{
    Console.WriteLine(name); // 巧克力, 奶酪, 面包
}

3. 返回的集合——都有哪些可能?

注意,Select总是返回一个集合(准确说是IEnumerable<TOutput>),TOutput就是你函数返回的类型。你自己决定它是什么:字符串、数字、匿名类型,甚至新对象都行。

投影成新类型

比如你想要的不是原始对象,而是它的“投影”:

var projections = products.Select(p => new { p.Name, 含税价 = p.Price * 1.2 });

foreach (var item in projections)
{
    Console.WriteLine($"{item.Name}: {item.含税价}");
}

这里我们造了个匿名对象的新集合——带名字和想象中的含税价!

4. 返回新类或匿名类型

你可以选:要么专门写个类做结果,要么用匿名类型,反正结构只在“这里和现在”用。

匿名类型

var result = products.Select(p => new { 名称 = p.Name, 原价 = p.Price, 新价 = p.Price + 40 });
foreach (var p in result)
{
    Console.WriteLine($"{p.名称}: 原来 {p.原价}, 现在 {p.新价}");
}

匿名类型很适合临时拼结果,尤其是没必要为一次操作专门写类的时候。

用自定义类做投影

新建一个类:

public class ProductDto
{
    public string Name { get; set; }
    public double PriceWithTax { get; set; }
}

然后在LINQ里用:

var productsWithTax = products.Select(p => new ProductDto
{
    Name = p.Name,
    PriceWithTax = p.Price * 1.2
});

foreach (var p in productsWithTax)
{
    Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}

5. 访问内部集合和属性

如果对象里有集合怎么办?比如每个用户有个订单列表。

public class User
{
    public string Name { get; set; }
    public List<Product> Purchases { get; set; }
}

你想拿到所有的购买列表?没问题!

var allPurchases = users.Select(u => u.Purchases);

foreach (var purchaseList in allPurchases)
{
    foreach (var product in purchaseList)
    {
        Console.WriteLine(product.Name);
    }
}

注意! 这样拿到的是“集合的集合”,不是扁平的商品列表。怎么拿到所有用户的所有商品合成一个列表?下节课讲——我们会用SelectMany。

6. 有用的小细节

转成集合类型:ToList()等

Select返回IEnumerable<T>。如果你想要普通的列表(List<T>),直接加.ToList():

var nameList = products.Select(p => p.Name).ToList();

现在你有个标准列表,可以随便用。

用元素的索引

有时候你想知道当前处理的是第几个元素。Select有个重载:

var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
    Console.WriteLine($"{item.Index}: {item.Name}");
}

这样可以,比如做带编号的列表数据。

编程里的投影

Select用得溜,面试经常会问。没有它,现代应用都很难写:

  • 只从数据库或外部服务拿需要的数据。
  • 准备数据,在应用层之间传递。
  • 把数据转成适合展示或网络传输的格式。
  • 省内存(只拿需要的字段,不拖整个对象)。

做大数据的公司,很多核心业务逻辑都靠这个。

7. 常见错误和注意点

新手经常会遇到烦人的bug——通常是没搞懂LINQ里的延迟执行(deferred execution)。比如,调用Select后,不一定马上执行查询——只有你真的遍历集合(foreach、ToList()等)才会执行。这让你能搭“处理管道”,但有时候会导致如果原始数据在用结果前被改了,结果也跟着变。

还有,Select不会改原集合——它会生成新集合。所以你如果还指望在products里看到新字段,products.Select(...)后是不会有的。

处理null: 如果投影时要访问可能是null的字段,记得小心点。在C# 8+里,如果有可能出NullReferenceException,编译器会警告你。

2
任务
C# SELF, 第 31 级, 课程 3
已锁定
对象投影并计算新值
对象投影并计算新值
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION