Unity的C#编程教程_62_语言集成查询 LINQ 详解及应用练习

目录:

LINQ: Querys

1. Any

  • LINQ: Language Integrated Query 语言集成查询

  • 类似 MySQL,允许过滤数据(array 和 list)

  • 任务说明:

    • 建立一个名字的数组
    • 查询某个名字是否在该数组中

之前的做法:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LinqTest : MonoBehaviour
{
    public string[] names = { "xiaoA", "xiaoB", "xiaoC", "xiaoD" };

    // Start is called before the first frame update
    void Start()
    {
        foreach(var name in names)
        {
            if(name == "xiaoC")
            {
                Debug.Log("Name found!");
            }
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

改用 LINQ:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    public string[] names = { "xiaoA", "xiaoB", "xiaoC", "xiaoD" };

    // Start is called before the first frame update
    void Start()
    {
        /*
        foreach(var name in names)
        {
            if(name == "xiaoC")
            {
                Debug.Log("Name found!");
            }
        }
        */

        var answer = names.Any(name => name == "xiaoE");
        // 在 names 数组中,是否有任何满足条件的元素?
        // 条件是 name == "xiaoC"
        // 返回的是 bool 值
        Debug.Log("Fond xiaoE? Answer: " + answer);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

2. Contains

  • 用于判断是否存在具体的元素
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    public string[] names = { "xiaoA", "xiaoB", "xiaoC", "xiaoD" };

    // Start is called before the first frame update
    void Start()
    {
        /*
        foreach(var name in names)
        {
            if(name == "xiaoC")
            {
                Debug.Log("Name found!");
            }
        }
        */

        /*
        var answer = names.Any(name => name == "xiaoE");
        // 在 names 数组中,是否有任何元素可以满足特定条件?
        // 条件是 name == "xiaoC"
        // 返回的是 bool 值
        */

        var answer = names.Contains("xiaoE");

        Debug.Log("Fond xiaoE? Answer: " + answer);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

3. Distinct

  • 去除重复元素
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    public string[] names = new string[] { "xiaoA", "xiaoA", "xiaoB", "xiaoC", "xiaoD" };

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Before");
        foreach (string name in names)
        {
            Debug.Log(name);
        }


        var newNames = names.Distinct();
        // Distinct 不会直接作用于原数组,所以需要赋值到一个新的变量

        Debug.Log("New");
        foreach (var name in newNames)
        {
            Debug.Log(name);
        }

        Debug.Log("After");
        foreach (var name in names)
        {
            Debug.Log(name);
        }
        
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

注意,Distinct 不会直接修改原数组。

4. Where

  • 为序列按照指定条件筛选
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    public string[] names = new string[] { "xiaoAA", "xiaoA", "xiaoBB", "xiaoCCCC", "xiaoDDDD" };

    // Start is called before the first frame update
    void Start()
    {
        var newNames = names.Where(name => name.Length > 6);
        // 对于原来数组中的元素进行筛选
        // 筛选条件是:长度大于 6

        foreach(var name in newNames)
        {
            Debug.Log(name);
        }
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Challenge: Hands on with LINQ

  • 任务说明:
    • 建立一个数组,包含的都是整型元素,每个元素取值范围 0~100
    • 筛选出 50 以上的数据
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    public int[] nums = { 10, 20, 35, 66, 78, 98, 45, 23, 67 };

    // Start is called before the first frame update
    void Start()
    {
        var newNums = nums.Where(num => num > 50);

        foreach(var num in newNums)
        {
            Debug.Log(num);
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

LINQ: Order by Descending

  • 筛选后进行排序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    public int[] nums = { 10, 20, 35, 66, 78, 98, 45, 23, 67 };

    // Start is called before the first frame update
    void Start()
    {
        var newNums = nums.Where(num => num > 50).OrderByDescending(n => n);
        // 筛选出大于 50 的元素,然后直接按照降序排列
        // 排序条件可以定制,比如设定 n => -n,其实就按照升序排列了

        foreach (var num in newNums)
        {
            Debug.Log(num);
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Challenge: Filter Items with LINQ

  • 任务说明:
    • 创建一个 class,用作筛选
    • 筛选对象为游戏道具
    • 查询 id 为 3 的物品是否存在,打印查询结果
    • 筛选 50 元以上的物品,打印查询结果
    • 计算所有道具的平均价格

创建基本脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

[System.Serializable] // 在 inspector 中可见
public class Item
{
    public string name;
    public int id;
    public int price;
}

public class LinqTest : MonoBehaviour
{
    public List<Item> items;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

然后在 inspector 中创建一些 item。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

[System.Serializable] // 在 inspector 中可见
public class Item
{
    public string name;
    public int id;
    public int price;
}

public class LinqTest : MonoBehaviour
{
    public List<Item> items;

    // Start is called before the first frame update
    void Start()
    {
        var result = items.Any(item => item.id == 3);
        Debug.Log("Fond id of 3: " + result);

        var newItems = items.Where(item => item.price > 50);
        foreach(var item in newItems)
        {
            Debug.Log("Item: " + item.name + " Price: " + item.price);
        }

        var total = items.Sum(item => item.price); // 求总价
        float averagePrice = (float)total / items.Count(); // 平均价
        // 为了确保平均价格为浮点数,这里把 total 设置为 float
        Debug.Log("Average price: " + averagePrice);

        // 也可以直接使用 average:
        var average = items.Average(item => item.price);
        Debug.Log("Average price: " + average);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

LINQ: How to Read and Convert Query Syntax

  • LINQ 的另一种使用方法:查询语法(query syntax)

微软官方示例:

class LINQQueryExpressions
{
    static void Main()
    {

        // Specify the data source.
        int[] scores = new int[] { 97, 92, 81, 60 };

        // Define the query expression.
        IEnumerable<int> scoreQuery =
            from score in scores
            where score > 80
            select score; // select 语句是必须的

        // Execute the query.
        foreach (int i in scoreQuery)
        {
            Console.Write(i + " ");
        }
    }
}
// Output: 97 92 81

放到 unity 的脚本中:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    // Specify the data source.
    int[] scores = new int[] { 97, 92, 81, 60 };

    // Start is called before the first frame update
    void Start()
    {
        // Define the query expression.
        IEnumerable<int> scoreQuery =
            from score in scores
            where score > 80
            select score;

        // Execute the query.
        foreach (int i in scoreQuery)
        {
            Debug.Log(i + " ");
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

这里使用的就是查询语法(query syntax),如何转化为 method syntax 呢:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq; // LINQ 需要使用这个库

public class LinqTest : MonoBehaviour
{
    // Specify the data source.
    int[] scores = new int[] { 97, 92, 81, 60 };

    // Start is called before the first frame update
    void Start()
    {
        // Define the query expression.
        /*
        IEnumerable<int> scoreQuery =
            from score in scores
            where score > 80
            select score;
        */
        var scoreQuery = scores.Where(score => score > 80);

        // Execute the query.
        foreach (int i in scoreQuery)
        {
            Debug.Log(i + " ");
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

推荐使用 method syntax,更简洁易用。

Tags: