找回密码
 立即注册
快捷导航

[.NET] 【杨中科】22-31 学习笔记

[复制链接]
妙笔生花 2023-9-5 05:16:41 | 显示全部楼层
本帖最后由 妙笔生花 于 2023-9-5 05:18 编辑

22、为啥要学 Linq

让数据处理变得简单:

// 统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。
var items = s.Where(c => char.IsLetter(c))//过滤非字母
    .Select(c => char.ToLower(c))//大写字母转换为小写
    .GroupBy(c => c)//根据字母进行分组
    .Where(g=>g.Count()>2)//过滤掉出现次数<=2
    .OrderByDescending(g => g.Count())//按次数排序
    .Select(g=>new { Char=g.Key,Count=g.Count()});

要想了解 Linq,必须会 Lambda,要想会 Lambda,必须会委托;

委托:是可以指向方法的类型,调用委托变量时执行的就是变量指向的方法。

delegate void D1();
class Program{
    static void Main(string[] args){
        D1 d = F1;
        d();
        d = F2;
        d();
    }
    static void F1(){
        Console.WriteLine("i am F1.");
    }

    static void F2(){
        Console.WriteLine("i am F2.");
    }
}

.NET 中定义了泛型委托Action(无返回值)和Func(有返回值),所以一般不用自定义委托类型。

class Program{
    static void Main(string[] args){
        Action a = F1;
        a += F2;
        Func<string, int> b = F3;        
        Console.ReadKey();
    }
    static void F1(){
        Console.WriteLine("i am F1.");
    }

    static void F2(){
        Console.WriteLine("i am F2.");
    }

    static int F3(string str){
        return str.Length;
    }
}

23、Linq是怎么来的

委托 + 匿名方法

Func<int, int, string> f1 = delegate (int i1, int i2) {
    return $"{i1}+{i2}={i1 + i2}";
};
string s = f1(1, 2);

匿名方法可以写成lambda表达式

Func<int, int, string> f1 = (i1,i2) =>{
    return $"{i1}+{i2}={i1 + i2}";
};

可以省略参数数据类型,因为编译能根据委托类型推断出参数的类型

如果委托没有返回值,且方法体只有一行代码,可省略 {}

Action<int, string> a1 = (age, name) => Console.WriteLine($"年龄{age},姓名{name}");
a1(18, "yzk");

如果=>之后的方法体中只有一行代码,且方法有返回值,那么可以省略方法体的{}以及return。

Func<int, int, string> f1 = (i1, i2) => $"{i1}+{i2}={i1 + i2}";

如果只有一个参数,参数的()可以省略。

Action<string> f1 = s => Console.WriteLine(s);

24、揭秘LINQ方法的背后

LINQ中提供了很多集合的扩展方法,配合lambda能简化数据处理。

int[] nums = new int[] { 3,99,88,77,7,8,9,66,15,7};
IEnumerable<int> items = nums.Where(i=>i>10); //using System.Linq;

使用var让编译器的“类型推断”来简化类型的声明。在LINQ中常用。

C#的var和JavaScript的var不一样,仍然是强类型的。

(*)C#中的弱类型是dynamic。

编写自己的扩展方法MyWhere来模拟Where的实现。

static IEnumerable<int> MyWhere(int[] items, Func<int, bool> filter)
{
    List<int> result = new();
    foreach (int i in items)
    {
        if (filter(i))
        {
            result.Add(i);
        }
    }
    return result;
}

int[] nums = new int[] { 3, 99, 88, 77, 7, 8, 9, 66, 15, 7 };
IEnumerable<int> items = MyWhere(nums, i => i > 10);

通过yield return来让MyWhere“流水线”处理。

static IEnumerable<int> MyWhere(IEnumerable<int> items, Func<int, bool> filter)
{
    List<int> result = new();
    foreach (int i in items)
    {
        if (filter(i))
        {
            yield return i;
        }
    }
}

int[] nums = new int[] { 3, 99, 88, 77, 7, 8, 9, 66, 15, 7 };
IEnumerable<int> items = MyWhere(nums, i => i > 10);
foreach (int i in items)
{
    Console.Out.WriteLine(i.ToString());
}
Console.ReadKey();

25、常用扩展方法1 Where/Count/Any

LINQ中提供了大量类似Where的扩展方法,简化数据处理。且这些扩展方法都是对 IEnumerable<T> 的扩展方法;

数组、List、Dictionary、Set等都实现了 IEnumerable 接口;

class Employee
{
    public long Id { get; set; }
    public string Name { get; set; }//姓名
    public int Age { get; set; }//年龄
    public bool Gender { get; set; }//性别
    public int Salary { get; set; }//月薪
    public override string ToString()
    {
        return $"Id={Id},Name={Name},Age={Age},Gender={Gender},Salary={Salary}";
    }
}

List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });

Where方法:每一项数据都会经过predicate的测试,如果针对一个元素,predicate执行的返回值为true,那么这个元素就会放到返回值中。

public static int Count<TSource>(this.IEnumerable source, Func<TSource, bool> predicate);

Where参数是一个lambda表达式格式的匿名方法,方法的参数e表示当前判断的元素对象。参数的名字不一定非要叫e,不过一般lambda表达式中的变量名长度都不长。

 IEnumerable<Employee> items1 = list.Where(e => e.Age > 30);

Count()方法:获取数据条数

int count1 = list.Count(e => e.Salary > 5000 || e.Age < 30);
int count2 = list.Where(e => e.Salary > 5000 || e.Age < 30).Count();

Any()方法:是否至少有一条数据,如果一条都没有,返回False,否则返回True

bool b1 = list.Any(e => e.Salary > 8000);
bool b2 = list.Where(e => e.Salary > 8000).Any(); 

有可能比Count()实现效率高。

26、常用扩展方法2 Single/First/Order/Skip/Take

获取一条数据(是否带参数的两种写法):

  • Single:有且只有一条满足要求的数据;查询1条数据,如果查询出有多条或者没有就报错;
  • SingleOrDefault :最多只有一条满足要求的数据;查询1条数据,查询有多条就报错,没有就返回数据默认值比如 null/0;
  • First :至少有一条,返回第一条;查询1条数据,有多条也返回1条,没有就报错;
  • FirstOrDefault :返回第一条或者默认值;查询1条数据,有多条也返回1条,没有就返回数据默认值比如 null/0;

排序:

  • Order() 对数据正序排序;
  • OrderByDescending() 倒序排序;

list.OrderBy(e => e.Age);   

对于简单类型排序

nums.OrderBy(e => e);   

特殊案例:

按照最后一个字符排序;

list.OrderBy(e=> e.Name[e.Name.Length-1]);

用Guid或者随机数进行随机排序。

list.OrderBy(e=>Guid.NewGuid());list.OrderBy(e=>new Random().Next());

多规则排序:

可以在Order()、OrderByDescending()后继续写ThenBy () 、ThenByDescending()。

案例:优先按照Age排序,如果Age相同再按照Salary排序,如下:

list.OrderBy(e => e.Age).ThenByDescending(e => e.Salary)

千万不要写成:list.OrderBy(e => e.Age). OrderByDescending (e => e.Salary)

限制结果集,获取部分数据:

Skip(n)跳过n条数据,Take(n)获取n条数据。

案例:获取从第2条(不包括)开始获取3条数据

var orderedItems1 = list.Skip(2).Take(3);

Skip()、Take()也可以单独使用。

27、常用扩展方法3 聚合函数Max/Min/Average/Sum/Count 分组GroupBy

聚合函数Max/Min/Average/Sum/Count 分组GroupBy

LINQ中所有的扩展方法几乎都是针对IEnumerable接口的,而几乎所有能返回集合的都返回IEnumerable,所以是可以把几乎所有方法“链式使用”的。

年龄大于30岁的最低工资:list.Where(e => e.Age > 30).Min(e=>e.Salary)

年龄大于30岁的平均工资:list.Where(e => e.Age > 30).Average(e=>e.Salary)

GroupBy()方法参数是分组条件表达式,返回值为 IGrouping<TKey, TSource> 类型的泛型IEnumerable(即:IEnumerable<IGrouping<TKey, TSource>>),也就是每一组以一个IGrouping对象的形式返回。IGrouping是一个继承自IEnumerable的接口,IGrouping中Key属性(TKey Key{get;set;})表示这一组的分组数据的值。

例子:根据年龄分组,获取每组人数、最高工资、平均工资。用var简化编程。

IEnumerable<IGrouping<int, Employee>> items = list.GroupBy(e=>e.Age);
foreach(IGrouping<int, Employee> g in items){
    Console.WriteLine(g.Key);
    Console.WriteLine("人数:" + g.Count());
    Console.WriteLine("最大工资:" + g.Max(a=>a.Salary));
    Console.WriteLine("平均工资:" + g.Average(a=>a.Salary));
    foreach(Employee e in g){
        Console.WriteLine(e);
    }
    Console.WriteLine("********************");
}

28、常用扩展方法4 投影/匿名类型/

什么是投影:把集合中的每一项转换为另外一种类型。(说白了就是 Select 用法)

IEnumerable<int> ages = list.Select(e => e.Age);
IEnumerable<string> names = list.Select(e=>e.Gender?"男":"女");
IEnumerable<Dog> dogs = list.Select(p => new Dog{NickName=e.Name,Age=e.Age});

匿名类型:

var p = new {Name="tom",Id=1};
var p1 = new {name,Id=1,p.Age};

通过反编译看匿名类型原理。

投影与匿名类型结合:

var items = list.Select(e=>new {e.Name,e.Age,XingBie= e.Gender ? "男" : "女"});

var items = list.GroupBy(e => e.Gender)
    .Select(g=>
            new { Gender=g.Key,Count=g.Count(),AvgSalary= g.Average(e => e.Salary),MinAge= g.Min(e => e.Age)}a
   );

29、集合转换/链式调用

集合转换:有一些地方需要数组类型或者List类型的变量,我们可以用ToArray()方法和ToList()分别把 IEnumerable<T> 转换为数组类型和 List<T> 类型。

List<Employee> lst = list.Where(e=>e.Salary>1000).ToList();
Employee[] arr = list.Where(e=>e.Salary>1000).ToArray();

链式调用

Where、Select、OrderBy、GroupBy、Take、Skip等返回值都是IEnumerable<T>类型,所以可以链式调用。

例子:“获取Id>2的数据,然后按照Age分组,并且把分组按照Age排序,然后取出前3条,最后再投影取得年龄、人数、平均工资”

var items = list.Where(x=>x.Id>2)
    .GroupBy(x=>x.Age)
    .OrderBy(x=>x.Key)
    .Take(3)
    .Select(g=> 
            new {NL=g.Key, RS=g.Count(), PJ=g.Average(e=>e.Salary)}
           );

30、LINQ另一种倩影

查询语法

使用Where、OrderBy、Select等 扩展方法进行数据查询的写法叫做 “LINQ方法语法”。还有一种“查询语法”的写法。

var items2 = from e in list
    where e.Salary > 3000
    orderby e.Age
    select new { e.Name, e.Age, Gender = e.Gender ? "男" : "女" };
// 注意这里的 new {e.Name 等同于 new {Name = e.Name

区别

运行时没有区别,编译后都是一样的。反编译一下看看。

“查询语法”看起来更酷,但是“方法语法”更实用,因此.NET开发者大部分还是用“方法语法”。

31、LINQ 面试问题

性能与面试

LINQ大部分时间不会影响性能,不过我曾经遇到过,讲讲。

比如图像像素渲染,有三个整数,取最大值,如果用linq:

int r =2, g=3, b=5;
var nums = new int[]{r, g, b};
nums.Max();

// 因为渲染的每个像素都要执行上述创建数据操作,极大消耗资源
// 可以用三目运算 r>g ? (r>b ? r: b) : (g > b ? g : b); 或者 Math.Max(r, Math.Max(g, b))

面试时候的算法题一般尽量避免使用正则表达式、LINQ等这些高级的类库。

大饭店面试大厨的故事。大厨面试一般考察蛋炒饭,不会让你做鲍鱼龙虾,因为鲍鱼龙虾本来就鲜美,是其本身材料问题,而蛋炒粉恰好能考察这个厨师对这种家常菜的最好吃做法;

案例1

有一个用逗号分隔的表示成绩的字符串,如"61,90,100,99,18,22,38,66,80,93,55,50,89",计算这些成绩的平均值。

"61,90,100,99,18,22,38,66,80,93,55,50,89".Split(',').Average(x=>x.Convert.ToInt32(e));

案例2

统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。

"hello,world.".ToUpper().ToList().GroupBy(t => t).Where(t => t.Count() > 2).OrderByDescending(o => o.Count());

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

温馨提示

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

Archiver|手机版|小黑屋|DLSite

GMT+8, 2024-11-22 16:33

Powered by Discuz! X3.5 and PHP8

快速回复 返回顶部 返回列表