集合本质上是一个类,最起码实现IEnumerable
其他集合接口有:
IList
IDictionary
ICollection
C#中的集合包括:Stack、Queue、List、Hashtable、Dictionary等。
在List
BinarySearch() 采用二叉搜索算法,但要求元素已排好序。如果事先没有排好序,即使元素存在也可能找不到。BinarySearch()一个有用的功能是,假如没有找到指定的元素,会返回一个负整数。该值的按位取反(~)结果是“大于被查找元素的下一个元素”的索引,如果没有更大的值,就返回(~元素总数)
List list = new List() { "private","protect","public"};
list.Sort();
int search = list.BinarySearch("protect internal");
if(search<0)
{
list.Insert(~search, "protect internal");
}
foreach (var item in list)
{
Console.Write(item+", ");
}
private, protect, protect internal, public,
在集合实例化期间用一组初始成员构造该集合
List list = new List() { "a", "b", "c" };
或:
List list = new List { "a", "b", "c" };
字典的初始化语法稍复杂点:
//C# 6.0 or later
Dictionary colorMap1 = new Dictionary
{
["a"]=ConsoleColor.Green,
["b"]=ConsoleColor.Blue,
["c"]=ConsoleColor.Red,
};
//before C#6.0
Dictionary colorMap2 = new Dictionary
{
{"a",ConsoleColor.Red},
{"b",ConsoleColor.Green},
{"c",ConsoleColor.Blue},
};
foreach使用迭代器模式遍历集合,不依赖索引,不需要事先知道集合中有多少元素
编译器禁止对foreach变量赋值。在foreach执行期间,集合中的元素计数不能变,集合项本身也不能更改
输出是一个新的 IEnumerable
List list1 = new List { "aa","abc","bc"};
List list2 = list1.Where(item => item.StartsWith("a")).ToList();
list2.ForEach(item => Console.WriteLine(item));
aa
abc
可以对集合中的每个数据项进行类型转换
//投射成FileInfo
IEnumerable fileList = Directory.GetFiles("D:");
IEnumerable fileInfos = fileList.Select(file => new FileInfo(file));
//投射成元组
IEnumerable fileNames = Directory.EnumerateFiles("D:");
IEnumerable<(string fileName, int size)> items = fileList.Select(file =>
{
FileInfo fileInfo = new FileInfo(file);
return (fileName: fileInfo.Name, size: file.Length);
});
List list1 = new List { "aa","abc","bc"};
var count=list1.Count;
Console.WriteLine(list1.Count(item => item.StartsWith("a")).ToString());
2
如果计数只是看个数是否大于0,优先选用 Enumerable.Any(),Count()会遍历集合,Any()只尝试遍历集合中的一个项,成功就返回true,而不是遍历整个序列。
linq有一个重要的概念是延迟执行,即lambda表达式在声明时不执行,在调用时才开始执行。
查询对象代表的是查询而非结果。每次调用查询对象都会执行查询,因为查询对象不确定上次执行的结果现在是否发生改变。
如果要避免反复执行,一次查询执行后,可将结果缓存起来,如使用ToXXX(ToArray()等)方法把结果赋值给一个变量,对赋值的变量进行遍历,就不再涉及查询表达式。
IEnumerable list = new List { "aa","abc","bc"};
bool result=false;
list = list.Where(item =>
{
//谓词通常只能做一件事情:对一个条件进行求值。它不应该有任何"副作用",包括打印
if (result = item.StartsWith("a"))
{
Console.WriteLine("\t" + item);
}
return result;
});
//foreach触发Lambda表达式执行
Console.WriteLine("1、 the result is:");
foreach (var item in list)
{
}
Console.WriteLine();
//调用Count()会为每一项触发Lambda表达式
Console.WriteLine("2、 the result is:");
Console.WriteLine(list.Count());
Console.WriteLine();
//调用ToList()、ToArray()、ToDictionary()、ToLookup()会为每一项触发Lambda表达式
Console.WriteLine("3、 the result is:");
Console.WriteLine(list.ToList());
OrderBy() 获取一个Lambda表达式,该表达式标识了排序要依据的键。
开始访问集合成员时排序才会生效。除非拿到所有需要排序的项,否则无法排序,因为无法确定是否已获得第一项。
要为一次排序后的列表继续排序需要调用ThenBy() 。
重复调用OrderBy() 会冲掉前面OrderBy()的结果。多个OrderBy() 共存时只有最后一个生效。
在客户端,对象和对象的关系一般已经建立好了,但从非对象存储加载的数据通常不是这种情况。这些数据需要联接到一起,以便以适合数据的方式从一种对象类型切换到另一种。
内部连接也成为同等连接,因为它们基于同等的键求值——只有在两个集合中都有对象,它们的记录才会出现在结果集中。
示例数据:
internal class Department
{
public long Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
internal class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public int DepartmentId { get; set; }
public override string ToString()
{
return $"{Name} ({Title})";
}
}
internal static class CorporateData
{
public static readonly Department[] Departments = new Department[]
{
new Department() { Id = 0, Name = "Corporate" },
new Department() { Id = 1, Name = "Engineer" },
new Department() { Id = 2, Name = "Store" },
new Department() { Id = 3, Name = "IT" },
new Department() { Id = 4, Name = "HR" },
};
public static readonly Employee[] Employees = new Employee[]
{
new Employee{Name="Mark",Title="CEO",DepartmentId=0},
new Employee{Name="Michael",Title="CFO",DepartmentId=4},
new Employee{Name="Brian",Title="CF",DepartmentId=3},
new Employee{Name="Ann",Title="BE",DepartmentId=4},
new Employee{Name="Pat",Title="LA",DepartmentId=2},
new Employee{Name="Kevin",Title="XIA",DepartmentId=1},
new Employee{Name="Thomas",Title="CIS",DepartmentId=3},
new Employee{Name="Eric",Title="SOS",DepartmentId=4},
};
}
public static void Main(string[] args)
{
IEnumerable departments = CorporateData.Departments;
Print(departments);
IEnumerable employees = CorporateData.Employees;
Print(employees);
}
private static void Print(IEnumerable items)
{
foreach (var item in items)
{
Console.WriteLine(item);
}
Console.WriteLine("----------------------");
}
Corporate
Engineer
Store
IT
HR
----------------------
Mark(CEO)
Michael(CFO)
Brian(CF)
Ann(BE)
Pat(LA)
Kevin(XIA)
Thomas(CIS)
Eric(SOS)
----------------------
Join连接:
//Join() 的第一个参数为inner,指定employees要联结到的集合
//接着两个参数都是lambda表达式,outerKeySelector和innerKeySelector,为两组数据指定键
//最后一个参数指定最终要选择的结果项
IEnumerable<(int Id,string Name,string Title,Department Department)> employeeItems = employees.Join(departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => (employee.Id, employee.Name,employee.Title, department));
foreach (var item in employeeItems )
{
Console.WriteLine($"{item.Name} ({item.Title})"+" "+ item.Department);
}
Console.WriteLine();
//反向连接
IEnumerable<(long Id, string Name, Employee Employee)> departmentItems = departments.Join(employees,
department => department.Id,
employee => employee.DepartmentId,
(department, employee) => (department.Id, department.Name, employee));
foreach (var item in departmentItems)
{
Console.WriteLine(item.Name + " " + item.Employee);
}
Mark (CEO) Corporate
Michael (CFO) HR
Brian (CF) IT
Ann (BE) HR
Pat (LA) Store
Kevin (XIA) Engineer
Thomas (CIS) IT
Eric (SOS) HRCorporate Mark(CEO)
Engineer Kevin(XIA)
Store Pat(LA)
IT Brian(CF)
IT Thomas(CIS)
HR Michael(CFO)
HR Ann(BE)
HR Eric(SOS)
使用GroupBy() 返回 IEnumerable
IEnumerable> groupedEmployees=employees.GroupBy(employee => employee.DepartmentId);
foreach (var group in groupedEmployees)
{
foreach (var item in group)
{
Console.WriteLine(item.DepartmentId + " " + item.Name);
}
}
0 Mark
4 Michael
4 Ann
4 Eric3 Brian
3 Thomas2 Pat
1 Kevin
IEnumerable<(long Id, string Name, IEnumerable Employees)> groupJoinItems = departments.GroupJoin(employees,
department => department.Id,
employee => employee.DepartmentId,
(department, departmentEmployees) => (department.Id, department.Name, departmentEmployees));
foreach (var group in groupJoinItems)
{
Console.WriteLine(group.Name);
foreach (var item in group.Employees)
{
Console.WriteLine(item);
}
Console.WriteLine();
}
Corporate
Mark (CEO)Engineer
Kevin (XIA)Store
Pat (LA)IT
Brian (CF)
Thomas (CIS)HR
Michael (CFO)
Ann (BE)
Eric (SOS)
外部联接:某些情况下,即使对应的对象不存在,也有必要创建一条记录,这时候,可以通过组合使用GroupJoin()、SelectMany() 和DefaultIfEmpty()来实现(左)外部联接。
IEnumerable departments = CorporateData.Departments;
IEnumerable employees = CorporateData.Employees;
var items = departments.GroupJoin(employees,
departments => departments.Id,
employees => employees.DepartmentId,
(department, departmentEmployees) => new
{
department.Id,
department.Name,
Employees = departmentEmployees
}).SelectMany(departmentRecord => departmentRecord.Employees.DefaultIfEmpty(),
(departmentRecord, employee) => new
{
departmentRecord.Id,
departmentRecord.Name,
Employees=departmentRecord.Employees
}).Distinct();
foreach (var item in items)
{
Console.WriteLine(item.Name);
foreach (var employee in item.Employees)
{
Console.WriteLine(employee);
}
Console.WriteLine();
}
Corporate
Mark (CEO)
Engineer
Kevin (XIA)
Store
Pat (LA)
IT
Brian (CF)
Thomas (CIS)
HR
Michael (CFO)
Ann (BE)
Eric (SOS)
SelectMany() 处理集合的集合。
Select() 可以一边投射一边转换,但项的数量不会发生改变。而SelectMany() 遍历由Lambda表达式标识的每一项,并将每一项都放到一个新的集合,新集合整合了子集合中的所有项。
(string Team, string[] Players)[] worldCup2006Finalists= new[]
{
(
TeamName:"France",
Players:new string[]
{
"Fabin","Fabin","Fabin",
}
),
(
TeamName:"France",
Players:new string[]
{
"Buffon","Buffon","Buffon"
}
)
};
IEnumerable players= worldCup2006Finalists.SelectMany(team=>team.Players);
foreach (var item in players)
{
Console.Write(item+" ");
}
Fabin Fabin Fabin Buffon Buffon Buffon
OfType:只返回特定类型的项
Union:合并两个集合,生成两个集合中的所有项的超集。结果集合不包含重复的项。
Concat:合并两个集合,生成两个集合中的所有项的超集,包含重复项。
Intersect:在结果集合中填充两个原始集合中所有的项
Distinct:筛选掉集合中重复的项
SequenceEquals:比较两个集合,返回bool值,指出集合是否一致,项的顺序也作为比较依据
Reverse:反转集合中各项的顺序。
匿名类型没有名称,但它仍是强类型的。
利用匿名类型可以对任意对象执行Select() 操作,只提取原始集合中满足当前算法的内容,而不必声明一个新类来专门包含这些内容。
编译器在生成匿名类型的代码时会重写ToString() 方法,对输出进行格式化,输出匿名类型会自动列出属性名及其值。类似的,生成的类型还会重写 Equals() 和 GetHashCode() 的实现。因此,一旦属性的顺序发生了变化,就会生成一个全新的数据类型。匿名类型一经实例化,再更改它的某个属性,就会造成编译错误。
IEnumerable fileList = Directory.EnumerateFiles("D:");
var files = fileList.Select(file =>
{
FileInfo fileInfo = new FileInfo(file);
return new
{
FileName = fileInfo.Name,
Size = fileInfo.Length
};
});
Print(files);
{ FileName = SFGH.docx, Size = 13263 }
匿名类型不能使用集合初始化器,因为集合初始化器要求执行一次构造函数调用,但根本没办法命名匿名类型的构造函数。