C#中的LINQ to Objects详解

目录
  • 一、概述
  • 二、 Linq to Objects中的延迟计算
    • 1. Linq延迟计算的注意点
    • 2. 整理Linq to Objects中运算符延迟计算特性
      • 1、具有延迟计算的运算符
      • 2、立即执行的运算符
  • 三、LINQ 和字符串
    • 1、查询文本块
      • 实例1: 统计单词在字符串中出现的次数
      • 实例2: 在文本文件中,找出包含一组指定单词的的句子。
      • 实例3: 查询字符串中的字符
      • 实例4:用正则表达式结合 LINQ 查询
    • 2、查询文本格式的半结构化数据
      • 实例1、如何查找两个集合间的差异
      • 实例2: 根据某字段进行排序或过滤文本行
      • 实例3、如何对一个分割的文件的字段重新排序
      • 实例4、如何组合和比较字符串集合
      • 实例5:从多个源填充对象集合
      • 实例6、如何向不同的文件中加入内容
      • 实例7:如何使用 group 将一个文件拆分成多个文件
      • 实例8、如何计算一个 CSV 文本文件中的列值
      • 实例9、从 CSV 文件生成 XML

一、概述

LINQ to Objects (C#) | Microsoft 官方文档

“LINQ to Objects” 指直接将 LINQ 查询与任何 IEnumerable 或 IEnumerable 集合一起使用。

可以使用 LINQ 来查询任何可枚举的集合,例如 List、Array 或Dictionary<TKey,TValue>。 该集合可以是用户定义的集合,也可以是由 .NET Framework API 返回的集合。

“LINQ to Objects”表示一种新的处理集合的方法。 采用旧方法,必须编写指定如何从集合检索数据的复杂的 foreach 循环。 而采用 LINQ 方法,只需编写描述要检索的内容的声明性代码。

此外,LINQ 查询与传统 foreach 循环相比具有三大优势:

  • 它们更简明、更易读,尤其在筛选多个条件时。
  • 它们使用最少的应用程序代码提供强大的筛选、排序和分组功能。
  • 无需修改或只需做很小的修改即可将它们移植到其他数据源。

二、 Linq to Objects中的延迟计算

Linq查询的延迟计算原理:通过给LINQ扩展方法传递方法委托,作为yield迭代器的主体,让遍历执行到MoveNext()时才执行耗时的指令。

1. Linq延迟计算的注意点

以下代码我们原本的期望结果是:删除掉字符串中所有的原音字母。但现在只删除’u’,因为item变量是循环外部声明的,同一个变量重复声明更新,所以当最后赋值时,item记录的是最后一个值为'u'。在foreach才真正执行Where查询。

IEnumerable<char> query = "Not what you might expect";
var item = 'a';
query = query.Where(c => c != item);
item = 'e';
query = query.Where(c => c != item);
item = 'i';
query = query.Where(c => c != item);
item = 'o';
query = query.Where(c => c != item);
item = 'u';
query = query.Where(c => c != item);

foreach (char c in query)
    Console.Write(c); // 只删除了'u'----Not what yo might expect

2. 整理Linq to Objects中运算符延迟计算特性

按字母顺序整理:

1、具有延迟计算的运算符

Cast,Concat,DefaultIfEmpty,Distinct,Except,GroupBy,GroupJoin,Intersect,Join,OfType,OrderBy,OrderByDescending ,Repeat,Reverse,Select,SelectMany,Skip,SkipWhile,Take,TakeWhile,ThenBy,ThenByDescending,Union,Where,Zip

2、立即执行的运算符

Aggregate,All,Any,Average,Contains,Count,ElementAt,ElementAtOrDefault,Empty,First,FirstOrDefault,Last,LastOrDefault ,LongCount,Max,Min,Range,SequenceEqual,Single,SingleOrDefault,Sum,ToArray,ToDictionary,ToList,ToLookup

注意:特殊的AsEnumerable运算符,用于处理LINQ to Entities操作远程数据源,将IQueryable远程数据立即转化为本地的IEnumerable集合。若AsEnumerable接收参数是IEnumerable内存集合则什么都不做。

三、LINQ 和字符串

LINQ 可用于查询和转换字符串和字符串集合。它对文本文件中的半结构化数据尤其有用。LINQ 查询可与传统的字符串函数和正则表达式结合使用。

例如:

  • 可以使用 Split 或 Split 方法来创建字符串数组,然后可以使用 LINQ 来查询或修改此数组;
  • 可以在 LINQ 查询的 where 子句中使用 IsMatch 方法;
  • 可以使用 LINQ 来查询或修改由正则表达式返回的 MatchCollection 结果

1、查询文本块

实例1: 统计单词在字符串中出现的次数

请注意,若要执行计数,请先调用 Split 方法来创建词数组。Split 方法存在性能开销。如果对字符串执行的唯一操作是计数词,则应考虑改用 Matches 或 IndexOf 方法。

string text = @"Historically, the world of data and the world of objects" +
          @" have not been well integrated. ";

string searchTerm = "data";

//字符串转换成数组
string[] source = text.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, StringSplitOptions.RemoveEmptyEntries);

// 创建查询,并忽略大小写比较
var matchQuery = from word in source
                 where word.ToLowerInvariant() == searchTerm.ToLowerInvariant()
                 select word;

// 统计匹配数量.
int wordCount = matchQuery.Count();
Console.WriteLine("{0} occurrences(s) of the search term \"{1}\" were found.", wordCount, searchTerm);
//1 occurrences(s) of the search term "data" were found.

实例2: 在文本文件中,找出包含一组指定单词的的句子。

在此示例中,查询将返回包含单词“Historically,”、“data,”和“integrated”的句子。 虽然在此示例中搜索条件数组是硬编码的,但也可以在运行时动态填充此数组。

string text = @"Historically, the world of data and the world of objects " +
    @"have not been well integrated. Programmers work in C# or Visual Basic " +
    @"and also in SQL or XQuery. On the one side are concepts such as classes, " +
    @"objects, fields, inheritance, and .NET Framework APIs. On the other side " +
    @"are tables, columns, rows, nodes, and separate languages for dealing with " +
    @"them. Data types often require translation between the two worlds; there are " +
    @"different standard functions. Because the object world has no notion of query, a " +
    @"query can only be represented as a string without compile-time type checking or " +
    @"IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
    @"objects in memory is often tedious and error-prone.";

// 将文本块切割成数组.
string[] sentences = text.Split(new char[] { '.', '?', '!' });

// 定义搜索条件,此列表可以运行时动态添加.
string[] wordsToMatch = { "Historically", "data", "integrated" };

// 去重,取交集后的数量对比
var sentenceQuery = from sentence in sentences
                    let w = sentence.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, StringSplitOptions.RemoveEmptyEntries)
                    where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
                    select sentence;

// 执行这个查询
foreach (string str in sentenceQuery)
{
    Console.WriteLine(str);
}
//Historically, the world of data and the world of objects have not been well integrated

查询运行时首先将文本拆分成句子,然后将句子拆分成包含每个单词的字符串数组。对于每个这样的数组,Distinct 方法移除所有重复的单词,然后查询对单词数组和 wordstoMatch 数组执行 Intersect 操作。如果交集的计数与 wordsToMatch 数组的计数相同,则在单词中找到了所有的单词,且返回原始句子。

在对 Split 的调用中,使用标点符号作为分隔符,以从字符串中移除标点符号。如果您没有这样做,则假如您有一个字符串“Historically,”,该字符串不会与 wordsToMatch 数组中的“Historically”相匹配。根据源文本中标点的类型,您可能必须使用其他分隔符。

实例3: 查询字符串中的字符

因为 String 类实现泛型 IEnumerable 接口,所以可以将任何字符串作为字符序列进行查询。但是,这不是 LINQ 的常见用法。若要执行复杂的模式匹配操作,请使用 Regex 类。

以下示例查询一个字符串以确定它所包含的数字数量。 请注意,在第一次执行此查询后将“重用”此查询。 这是可能的,因为查询本身并不存储任何实际的结果。

string aString = "ABCDE99F-J74-12-89A";

// 只选择数字的字符
IEnumerable<char> stringQuery =
  from ch in aString
  where Char.IsDigit(ch)
  select ch;

// 执行这个查询
foreach (char c in stringQuery)
    Console.Write(c + " ");
//9 9 7 4 1 2 8 9 

// 对上面的查询调用Count方法
int count = stringQuery.Count();
Console.WriteLine("Count = {0}", count);
//Count = 8

// 选择第一个“-”之前的所有字符
IEnumerable<char> stringQuery2 = aString.TakeWhile(c => c != '-');

// 执行第二个查询
foreach (char c in stringQuery2)
    Console.Write(c);
//ABCDE99F

实例4:用正则表达式结合 LINQ 查询

此示例演示如何使用 Regex 类创建正则表达式以便在文本字符串中进行更复杂的匹配。使用 LINQ 查询可以方便地对您要用正则表达式搜索的文件进行准确筛选,以及对结果进行加工。

//根据不同版本的 vs 修改路径
string startFolder = @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\";
IEnumerable fileList = Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories).Select(p => new System.IO.FileInfo(p));
//创建正则表达式来寻找所有的"Visual"
Regex searchTerm = new Regex(@"Visual (Basic|C#|C\+\+|Studio)");

//搜索每一个“.htm”文件
//通过 where 找到匹配项
//注意:select 中的变量要求显示声明其类型,因为 MatchCollection 不是泛型 IEnumerable 集合
var queryMatchingFiles =
    from file in fileList
    where file.Extension == ".htm"
    let fileText = File.ReadAllText(file.FullName)
    let matches = searchTerm.Matches(fileText)
    where matches.Count > 0
    select new
    {
        name = file.FullName,
        matchedValues = from Match match in matches
                        select match.Value
    };

foreach (var v in queryMatchingFiles)
{
    //修剪匹配找到的文件中的路径
    string s = v.name.Substring(startFolder.Length - 1);
    Console.WriteLine(s);

    //输出找到的匹配值
    foreach (var v2 in v.matchedValues)
    {
        Console.WriteLine("  " + v2);
    }
}

//\professional\2052\License.htm
//  Visual Studio
//  Visual Studio
//   Visual Studio
//\VB\VBWizards\FrameSetTemplates\1033\BanToc.htm
//   Visual Studio

还可以查询由 RegEx 搜索返回的 MatchCollection 对象。在此示例中,结果中仅生成每个匹配项的值。但也可使用 LINQ 对该集合执行各种筛选、排序和分组操作。

【注意】由于 MatchCollection 是非泛型 IEnumerable 集合,因此必须显式声明查询中的范围变量的类型。

2、查询文本格式的半结构化数据

许多不同类型的文本文件都包含一系列行,通常具有类似的格式设置,例如制表符分隔或逗号分隔的文件或固定长度的行。

将此类文本文件读入内存后,可以使用 LINQ 来查询和/或修改其中的行。 LINQ 查询还简化了合并来自多个源的数据的任务。

实例1、如何查找两个集合间的差异

此示例演示如何使用 LINQ 对两个字符串列表进行比较,并输出那些位于 names1.txt 中但不在 names2.txt 中的行。

names1.txt

Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Potra, Cristina 
Noriega, Fabricio 
Aw, Kam Foo 
Beebe, Ann 
Toyoshima, Tim 
Guy, Wey Yuan 
Garcia, Debra

names2.txt

Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

代码:

//创建数据源
var names1Text = File.ReadAllLines(@"names1.txt");
var names2Text = File.ReadAllLines(@"names2.txt");

//创建查询,这里必须使用方法语法
var query = names1Text.Except(names2Text);

//执行查询
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (var name in query)
{
    Console.WriteLine(name);
}

【注意】某些类型的查询操作(如 ExceptDistinctUnion 和 Concat)只能用基于方法的语法表示。

实例2: 根据某字段进行排序或过滤文本行

下面的示例演示如何按结构化文本(如逗号分隔值)行中的任意字段对该文本行进行排序。可在运行时动态指定该字段。此示例还演示如何从方法返回查询变量。

scores.csv:假定 scores.csv 中的字段表示学生的 ID 号,后面跟四个测验分数。

111, 97, 92, 81, 60 
112, 75, 84, 91, 39 
113, 88, 94, 65, 91 
114, 97, 89, 85, 82 
115, 35, 72, 91, 70 
116, 99, 86, 90, 94 
117, 93, 92, 80, 87 
118, 92, 90, 83, 78 
119, 68, 79, 88, 92 
120, 99, 82, 81, 79 
121, 96, 85, 91, 60 
122, 94, 92, 91, 91

// 创建数据源
string[] scores = System.IO.File.ReadAllLines(@"scores.csv");

// 可以改为 0~4 的任意值
int sortField = 1;

Console.WriteLine("Sorted highest to lowest by field [{0}]:", sortField);

//演示从方法返回查询,这里执行查询
foreach (string str in RunQuery(scores, sortField))
{
    Console.WriteLine(str);
}

// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit");

// 返回查询变量,非查询结果
static IEnumerable<string> RunQuery(IEnumerable<string> source, int num)
{
    // 分割字符串来排序
    var scoreQuery = from line in source
                     let fields = line.Split(',')
                     orderby fields[num] descending
                     select line;

    return scoreQuery;
}

/* Output (if sortField == 1):
   Sorted highest to lowest by field [1]:
    116, 99, 86, 90, 94
    120, 99, 82, 81, 79
    111, 97, 92, 81, 60
    114, 97, 89, 85, 82
    121, 96, 85, 91, 60
    122, 94, 92, 91, 91
    117, 93, 92, 80, 87
    118, 92, 90, 83, 78
    113, 88, 94, 65, 91
    112, 75, 84, 91, 39
    119, 68, 79, 88, 92
    115, 35, 72, 91, 70
 */

实例3、如何对一个分割的文件的字段重新排序

逗号分隔值 (CSV) 文件是一种文本文件,通常用于存储电子表格数据或其他由行和列表示的表格数据。通过使用 Split 方法分隔字段,可以非常轻松地使用 LINQ 来查询和操作 CSV 文件。事实上,可以使用此技术来重新排列任何结构化文本行部分;此技术不局限于 CSV 文件。

在下面的示例中,假定有三列分别代表学生的“姓氏”、“名字”和“ID”。这些字段基于学生的姓氏按字母顺序排列。查询生成一个新序列,其中首先出现的是 ID 列,后面的第二列组合了学生的名字和姓氏。根据 ID 字段重新排列各行。结果保存到新文件,但不修改原始数据。

spreadsheet1.csv

Adams,Terry,120 
Fakhouri,Fadi,116 
Feng,Hanying,117 
Garcia,Cesar,114 
Garcia,Debra,115 
Garcia,Hugo,118 
Mortensen,Sven,113 
O'Donnell,Claire,112 
Omelchenko,Svetlana,111 
Tucker,Lance,119 
Tucker,Michael,122 
Zabokritski,Eugene,121

代码

//数据源
var lines = File.ReadAllLines(@"spreadsheet1.csv");
//将旧数据的第2列的字段放到第一位,逆向结合第0列和第1列的字段
var query = from line in lines
            let t = line.Split(',')
            orderby t[2]
            select $"{t[2]}, {t[1]} {t[0]}";

foreach (var q in query)
{
    Console.WriteLine(q);
}

//写入文件
File.WriteAllLines("spreadsheet2.csv", query);

实例4、如何组合和比较字符串集合

此示例演示如何合并包含文本行的文件,然后排序结果。具体来说,此示例演示如何对两组文本行执行简单的串联、联合和交集。

names1.txt

Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Potra, Cristina 
Noriega, Fabricio 
Aw, Kam Foo 
Beebe, Ann 
Toyoshima, Tim 
Guy, Wey Yuan 
Garcia, Debra

names2.txt

Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

代码:

var names1Text = File.ReadAllLines(@"names1.txt");
var names2Text = File.ReadAllLines(@"names2.txt");

//简单连接,并排序。重复保存。
var concatQuery = names1Text.Concat(names2Text).OrderBy(x => x);
OutputQueryResult(concatQuery, "Simple concatenate and sort. Duplicates are preserved:");

//基于默认字符串比较器连接,并删除重复。
var unionQuery = names1Text.Union(names2Text).OrderBy(x => x);
OutputQueryResult(unionQuery, "Union removes duplicate names:");

//查找在两个文件中出现的名称
var intersectQuery = names1Text.Intersect(names2Text).OrderBy(x => x);
OutputQueryResult(intersectQuery, "Merge based on intersect:");

//在每个列表中找到匹配的字段。使用 concat 将两个结果合并,然后使用默认的字符串比较器进行排序
const string nameMatch = "Garcia";
var matchQuery1 = from name in names1Text
                  let t = name.Split(',')
                  where t[0] == nameMatch
                  select name;
var matchQuery2 = from name in names2Text
                  let t = name.Split(',')
                  where t[0] == nameMatch
                  select name;

var temp = matchQuery1.Concat(matchQuery2).OrderBy(x => x);
OutputQueryResult(temp, $"Concat based on partial name match \"{nameMatch}\":");

private static void OutputQueryResult(IEnumerable<string> querys, string title)
{
    Console.WriteLine(Environment.NewLine + title);
    foreach (var query in querys)
    {
        Console.WriteLine(query);
    }

    Console.WriteLine($"{querys.Count()} total names in list");
}

实例5:从多个源填充对象集合

不要尝试将内存中的数据或文件系统中的数据与仍在数据库中的数据相联接。此种跨域联接会生成未定义的结果,因为数据库查询和其他类型的源定义联接运算的方式可能不同。另外,如果数据库中的数据量足够大,则存在此类运算引发内存不足异常的风险。

若要将数据库数据与内存中的数据相联接,请首先对数据库查询调用 ToList 或 ToArray,然后对返回的集合执行联接。

//每行 names.csv 包含姓氏,名字,和身份证号,以逗号分隔。例如,Omelchenko,Svetlana,111
var names = File.ReadAllLines(@"names.csv");
//每行 scores.csv 包括身份证号码和四个测试评分,以逗号分隔。例如,111,97,92,81,60
var scores = File.ReadAllLines(@"scores.csv");

//使用一个匿名的类型合并数据源。
//【注意】动态创建一个 int 的考试成绩成员列表。
//跳过分割字符串中的第一项,因为它是学生的身份证,不是一个考试成绩
var students = from name in names
               let t = name.Split(',')
               from score in scores

               let t2 = score.Split(',')
               where t[2] == t2[0]
               select new
               {
                   FirstName = t[0],
                   LastName = t[1],
                   ID = Convert.ToInt32(t[2]),
                   ExamScores = (from scoreAsText in t2.Skip(1)
                                 select Convert.ToInt32(scoreAsText)).ToList()
               };
//显示每个学生的名字和考试平均成绩。
foreach (var student in students)
{
    Console.WriteLine(
        $"The average score of {student.FirstName} {student.LastName} is {student.ExamScores.Average()}.");
}

实例6、如何向不同的文件中加入内容

此示例演示如何联接两个逗号分隔文件中的数据,这两个文件共享一个用作匹配键的共同值。如果您必须将两个电子表格的数据或一个电子表格和一个其他格式的文件的数据组合为一个新文件,则此技术很有用。还可以修改此示例以适合任意种类的结构化文本。

names.csv:此文件表示一个电子表格。该电子表格包含学生的姓氏、名字和学生 ID。

Omelchenko,Svetlana,111 
O'Donnell,Claire,112 
Mortensen,Sven,113 
Garcia,Cesar,114 
Garcia,Debra,115 
Fakhouri,Fadi,116 
Feng,Hanying,117 
Garcia,Hugo,118 
Tucker,Lance,119 
Adams,Terry,120 
Zabokritski,Eugene,121 
Tucker,Michael,122

scores.csv:此文件表示电子表格数据。第 1 列是学生的 ID,第 2 至 5 列是测验分数。

111, 97, 92, 81, 60 
112, 75, 84, 91, 39 
113, 88, 94, 65, 91 
114, 97, 89, 85, 82 
115, 35, 72, 91, 70 
116, 99, 86, 90, 94 
117, 93, 92, 80, 87 
118, 92, 90, 83, 78 
119, 68, 79, 88, 92 
120, 99, 82, 81, 79 
121, 96, 85, 91, 60 
122, 94, 92, 91, 91

代码:

var names = File.ReadAllLines(@"names.csv");
var scores = File.ReadAllLines(@"scores.csv");

//Name:    Last[0],       First[1],  ID[2]
//          Omelchenko,    Svetlana,  11
//Score:   StudentID[0],  Exam1[1]   Exam2[2],  Exam3[3],  Exam4[4]
//          111,           97,        92,        81,        60

//该查询基于 id 连接两个不同的电子表格
var query = from name in names
            let t1 = name.Split(',')
            from score in scores
            let t2 = score.Split(',')
            where t1[2] == t2[0]
            orderby t1[0]
            select $"{t1[0]},{t2[1]},{t2[2]},{t2[3]},{t2[4]}";

//输出
OutputQueryResult(query, "Merge two spreadsheets:");

private static void OutputQueryResult(IEnumerable<string> querys, string title)
{
    Console.WriteLine(Environment.NewLine + title);
    foreach (var query in querys)
    {
        Console.WriteLine(query);
    }

    Console.WriteLine($"{querys.Count()} total names in list");
}

实例7:如何使用 group 将一个文件拆分成多个文件

此示例演示一种进行以下操作的方法:合并两个文件的内容,然后创建一组以新方式组织数据的新文件。

name1.txt

Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Potra, Cristina 
Noriega, Fabricio 
Aw, Kam Foo 
Beebe, Ann 
Toyoshima, Tim 
Guy, Wey Yuan 
Garcia, Debra 
name2.text 
Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

name2.txt

Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

代码:

var fileA = File.ReadAllLines(@"names1.txt");
var fileB = File.ReadAllLines(@"names2.txt");

//并集:连接并删除重复的名字
var mergeQuery = fileA.Union(fileB);
//根据姓氏的首字母对姓名进行分组
var query = from name in mergeQuery
            let t = name.Split(',')
            group name by t[0][0] into g
            orderby g.Key
            select g;

//为创建的每个组创建一个新文件,请注意,需要使用嵌套的foreach循环来访问每个组的单个项。
foreach (var g in query)
{
    var fileName = @"testFile_" + g.Key + ".txt";
    Console.WriteLine(g.Key + ":");

    //写入文件
    using (var sw = new StreamWriter(fileName))
    {
        foreach (var name in g)
        {
            sw.WriteLine(name);
            Console.WriteLine(" " + name);
        }
    }
}

对于与数据文件位于同一文件夹中的每个组,程序将为这些组编写单独的文件。

实例8、如何计算一个 CSV 文本文件中的列值

此示例演示如何对 .csv 文件的列执行诸如 Sum、Average、Min 和 Max 等聚合计算。此处所示的示例原则可以应用于其他类型的结构化文本。

scores.csv

111, 97, 92, 81, 60 
112, 75, 84, 91, 39 
113, 88, 94, 65, 91 
114, 97, 89, 85, 82 
115, 35, 72, 91, 70 
116, 99, 86, 90, 94 
117, 93, 92, 80, 87 
118, 92, 90, 83, 78 
119, 68, 79, 88, 92 
120, 99, 82, 81, 79 
121, 96, 85, 91, 60 
122, 94, 92, 91, 91

scores.csv:假定第一列表示学员 ID,后面几列表示四次考试的分数。

var scores = File.ReadAllLines(@"scores.csv");

//指定要计算的列
const int examNum = 3;

//scores.csv 格式:
//Student ID    Exam#1  Exam#2  Exam#3  Exam#4
//111,          97,     92,     81,     60

//+1 表示跳过第一列
//计算单一列
SingleColumn(scores, examNum + 1);
Console.WriteLine();

//计算多列
MultiColumns(scores);

private static void SingleColumn(IEnumerable<string> strs, int examNum)
{
    Console.WriteLine("Single Column Query:");

    //查询分两步:
    // 1.分割字符串
    // 2.对要计算的列的值转换为 int
    var query = from str in strs
                let t = str.Split(',')
                select Convert.ToInt32(t[examNum]);

    //对指定的列进行统计
    var average = query.Average();
    var max = query.Max();
    var min = query.Min();

    Console.WriteLine($"Exam #{examNum}: Average:{average:##.##} High Score:{max} Low Score:{min}");
}

private static void MultiColumns(IEnumerable<string> strs)
{
    Console.WriteLine("Multi Column Query:");

    //查询步骤:
    // 1.分割字符串
    // 2.跳过 id 列(第一列)
    // 3.将当前行的每个评分都转换成 int,并选择整个序列作为一行结果。
    var query = from str in strs
                let t1 = str.Split(',')
                let t2 = t1.Skip(1)
                select (from t in t2
                        select Convert.ToInt32(t));

    //执行查询并缓存结果以提高性能
    var results = query.ToList();
    //找出结果的列数
    var count = results[0].Count();

    //执行统计
    //为每一列分数的循环执行一次循环
    for (var i = 0; i < count; i++)
    {
        var query2 = from result in results
                     select result.ElementAt(i);

        var average = query2.Average();
        var max = query2.Max();
        var min = query2.Min();

        //+1 因为 #1 表示第一次考试
        Console.WriteLine($"Exam #{i + 1} Average: {average:##.##} High Score: {max} Low Score: {min}");
    }

}

查询的工作原理是使用 Split 方法将每一行文本转换为数组。每个数组元素表示一列。最后,每一列中的文本都转换为其数字表示形式。如果文件是制表符分隔文件,只需将 Split 方法中的参数更新为 \t。

实例9、从 CSV 文件生成 XML

下面的代码对字符串数组执行 LINQ 查询。

该查询使用 let 子句将每个字符串分隔成字段数组。

// Create the text file.
string csvString = @"GREAL,Great Lakes Food Market,Howard Snyder,Marketing Manager,(503) 555-7555,2732 Baker Blvd.,Eugene,OR,97403,USA
HUNGC,Hungry Coyote Import Store,Yoshi Latimer,Sales Representative,(503) 555-6874,City Center Plaza 516 Main St.,Elgin,OR,97827,USA
LAZYK,Lazy K Kountry Store,John Steel,Marketing Manager,(509) 555-7969,12 Orchestra Terrace,Walla Walla,WA,99362,USA
LETSS,Let's Stop N Shop,Jaime Yorres,Owner,(415) 555-5938,87 Polk St. Suite 5,San Francisco,CA,94117,USA";
File.WriteAllText("cust.csv", csvString);  

// Read into an array of strings.
string[] source = File.ReadAllLines("cust.csv");
XElement cust = new XElement("Root",
    from str in source
    let fields = str.Split(',')
    select new XElement("Customer",
        new XAttribute("CustomerID", fields[0]),
        new XElement("CompanyName", fields[1]),
        new XElement("ContactName", fields[2]),
        new XElement("ContactTitle", fields[3]),
        new XElement("Phone", fields[4]),
        new XElement("FullAddress",
            new XElement("Address", fields[5]),
            new XElement("City", fields[6]),
            new XElement("Region", fields[7]),
            new XElement("PostalCode", fields[8]),
            new XElement("Country", fields[9])
        )
    )
);
Console.WriteLine(cust);

到此这篇关于C#中LINQ to Objects的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#使用Object类实现栈的方法详解

    本文实例讲述了C#使用Object类实现栈的方法.分享给大家供大家参考,具体如下: Stack类的代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 使用Object类实现后进先出队列 { class Stack { private Object[] _items; public Object[] Items { get { return this.

  • C#使用dynamic类型访问JObject对象

    dynamic是C#里面的动态类型,可在未知类型的情况访问对应的属性,非常灵活和方便. 使用Json.Net可以把一个Json字符串转换成一个JObject对象,如果有已知强类型,如果有已知对应的强类型,可以直接转成对应的类型.但如果没有,要访问Json里面对应的数据的时候,就显得比较麻烦.我们可以借助DynamicObject来访问对应的属性. DynamicObject 我们要创建一个动态类,用于访问JObject,代码如下: public class JObjectAccessor : D

  • C#实现xml文件反序列化读入数据到object的方法

    本文实例讲述了C#实现xml文件反序列化读入数据到object的方法.分享给大家供大家参考.具体实现方法如下: public static object DeSerializeFromXmlString(System.Type typeToDeserialize, string xmlString) { byte[] bytes = System.Text.Encoding.UTF8.GetBytes(xmlString); MemoryStream memoryStream = new Mem

  • C# Dynamic关键字之:解析dynamic就是Object

    C# 4.0提供了一个dynamic 关键字,那么什么是dynamic,究竟dynamic是如何工作的呢? 从最简单的示例开始: 复制代码 代码如下: static void Main(string[] args)        {            dynamic dyn = 1;            object obj = 1;            //在编译时将鼠标放到 "dyn"  和"obj"中可以发现:             // dyn:局

  • c# 所有类型都从Object类型派生

    CLR要求每一个类型都最终从object类型派生,如下: class Typer {} === class Typer :object {} 上面是完全相等的. 所有的类型最终从object派生,每一个类型都有一组最基本的方法: Equals:如果两个对象相等,返回True GetHashCode:返回对象值的一个哈希码 ToString:默认返回类型的完整名称,int等类型被重写 GetType:返回调用的那个对象的类型 此外,从object派生类型能访问受保护的方法 MemberWiseCl

  • c#中object、var和dynamic的区别小结

    原文链接: Difference Between Object and Dynamic Keyword in C# Difference between var and dynamic in C# 篇一(dynamic与object的区别) 我们经常看到很多C#开发者并不能区分object与dynamic变量.我最近也尝试在网上找相关的教程和文章,不过还是没有找到比较好的解释.这篇文章将梳理object与dynamic区别的关键点. 总的来说,dynamic与object都不进行编译时类型检查(

  • C#中的LINQ to Objects详解(2)

    目录 四.Linq和反射 五.LINQ 和字符串 1.LINQ 和文件目录 实例1.如何查询具有指定属性或名称的文件 实例2.如何按照扩展名对文件进行分组 实例3.如何查询一组文件夹中的总字节数 实例4.如何比较两个文件夹中的内容 实例5.如何在目录树中查询最大的文件 实例6.如何在目录树中查询重复的文件 实例7.如何在文件夹中查询文件的内容 相关文章: C#中的LINQ to Objects详解(1) C#中的LINQ to Objects详解(2) 四.Linq和反射 .NET Framew

  • C#中的LINQ to Objects详解

    目录 一.概述 二. Linq to Objects中的延迟计算 1. Linq延迟计算的注意点 2. 整理Linq to Objects中运算符延迟计算特性 1.具有延迟计算的运算符 2.立即执行的运算符 三.LINQ 和字符串 1.查询文本块 实例1: 统计单词在字符串中出现的次数 实例2: 在文本文件中,找出包含一组指定单词的的句子. 实例3: 查询字符串中的字符 实例4:用正则表达式结合 LINQ 查询 2.查询文本格式的半结构化数据 实例1.如何查找两个集合间的差异 实例2: 根据某字

  • C#中的Linq to Xml详解

    前言 我相信很多从事.NET开发的,在.NET 3.5之前操作XML会比较麻烦,但是在此之后出现了Linq to Xml,而今天的主人公就是Linq to Xml,废话不多说,直接进入主题. 一.生成Xml 为了能够在结构有一定的组织,笔者建议大家新建一个控制台项目,并且新建一个CreateXml类(以下部分都属于该类中). 并在其中写入以下属性: 复制代码 代码如下: public static String Path         {             get            

  • C#中DataTable 转实体实例详解

    因为Linq的查询功能很强大,所以从数据库中拿到的数据为了处理方便,我都会转换成实体集合List<T>. 开始用的是硬编码的方式,好理解,但通用性极低,下面是控件台中的代码: using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Demo1 { class Pr

  • 微信小程序中实现一对多发消息详解及实例代码

    微信小程序中实现一对多发消息详解及实例代码 微信小程序中各个界面之间的传值和通知比较蛋疼.所以模仿了iOS中的通知中心,在微信小程序中写了一套类似的通知中心. 通知中心可以做到:1对多发消息,传递object.使用十分简洁. 使用时,在需要接收消息的界面注册一个通知名.然后在需要发消息的界面post这个通知名就可以了.可以在多个界面注册同一个通知名.这样就可以1对多发消息. 使用方法: 1:在app.js中引用notification.js var notificationCenter = re

  • Java语言中的内存泄露代码详解

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上. 下面看一个示例: public class Simple { public static vo

  • Django中的forms组件实例详解

    Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确.如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.. Django form组件就实现了上面所述的功能. 总结一下,其实form组件的主要功能如下: 生成页面可用的HTML标签 对用户提交的数据进行校验 保留上次输入内容 先在应用目录下my_for

  • django中的ajax组件教程详解

    Ajax(Asynchronous Javascript And XML)翻译成英文就是"异步Javascript和XML".即用Javascript语言与服务器进行异步交互,传输的数据为XML,(现在使用更多的是json数据). 向服务器发送请求的途径 1.浏览器地址栏 http://www.baidu.com 默认是get请求 2.form表单发送请求: GET请求 POST请求 3.a标签 href属性 默认是get请求 4.ajax() Ajax的特点 异步交互:客户端发送一个

  • Django中使用Celery的教程详解

    Django教程 Python下有许多款不同的 Web 框架.Django是重量级选手中最有代表性的一位.许多成功的网站和APP都基于Django. Django是一个开放源代码的Web应用框架,由Python写成. Django遵守BSD版权,初次发布于2005年7月, 并于2008年9月发布了第一个正式版本1.0 . Django采用了MVC的软件设计模式,即模型M,视图V和控制器C. 一.前言 Celery是一个基于python开发的分布式任务队列,如果不了解请阅读笔者上一篇博文Celer

  • C#中backgroundWorker类的用法详解

    1.在 WinForms 中,有时要执行耗时的操作,在该操作未完成之前操作用户界面,会导致用户界面停止响应.解决的方法就是新开一个线程,把耗时的操作放到线程中执行,这样就可以在用户界面上进行其它操作.新建线程可以用 Thread 类,可以实现多线程同时操作.简单的方法可以通过 BackgroundWorker 类实现. BackgroundWorker 可以用来更新UI界面,但是通常用来Progressbar(进度条)控件 例如更新UI private void Form1_Load(objec

随机推荐