文章目录
LINQ
LINQ 概述
LINQ(Language Integrated Query,语言集成查询)在C#编程语言中集成了查询语法,可以用相同的语法访问 不同的数据源。LINQ提供了不同数据源的抽象层,所以可以使用相同的语法。
列表和实体
- Racer定义了几个属性和一个重载的ToStringO方法,该方法以字符串格式显 示赛车手。
public class Racer : IComparable<Racer>, IFormattable
{
public Racer(string firstName, string lastName, string country, int starts, int wins, IEnumerable<int> years, IEnumerable<string> cars)
{
FirstName = firstName;
LastName = lastName;
Country = country;
Starts = starts;
Wins = wins;
Years = years != null ? new List<int>(years) : new List<int>();
Cars = cars != null ? new List<string>(cars) : new List<string>();
}
public Racer(string firstName, string lastName, string country, int starts, int wins)
: this(firstName, lastName, country, starts, wins, null, null) { }
public string FirstName { get; }
public string LastName { get; }
public string Country { get; }
public int Wins { get; }
public int Starts { get; }
public IEnumerable<string> Cars { get; }
public IEnumerable<int> Years { get; }
public override string ToString() => $"{FirstName} {LastName}";
public int CompareTo(Racer other) => LastName.CompareTo(other?.LastName);
public string ToString(string format) => ToString(format, null);
public string ToString(string format, IFormatProvider formatProvider)
{
switch (format)
{
case null:
case "N":
return ToString();
case "F":
return FirstName;
case "L":
return LastName;
case "C":
return Country;
case "S":
return Starts.ToString();
case "W":
return Wins.ToString();
case "A":
return $"{FirstName} {LastName}, country: {Country}; starts: {Starts}, wins: {Wins}";
default:
throw new FormatException($"Format {format} not supported");
}
}
}
- Team这个类仅包含车队冠军的名字和获得冠军的年份。与赛车手冠军类似,针对一年中 最好的车队也有一个冠军奖项
public class Team
{
public Team(string name, params int[] years)
{
Name = name;
Years = years != null ? new List<int>(years) : new List<int>();
}
public string Name { get; }
public IEnumerable<int> Years { get; }
}
- Formulal类在GetChampions()方法中返回一组赛车手。这个列表包含了 1950—2016年之间的所有一级方程式冠军
public static class Formula1
{
private static List<Racer> s_racers;
private static List<Racer> InitializeRacers() =>
new List<Racer>
{
new Racer("Nino", "Farina", "Italy", 33, 5, new int[] { 1950 }, new string[] { "Alfa Romeo" }),
new Racer("Alberto", "Ascari", "Italy", 32, 13, new int[] { 1952, 1953 }, new string[] { "Ferrari" }),
new Racer("Juan Manuel", "Fangio", "Argentina", 51, 24, new int[] { 1951, 1954, 1955, 1956, 1957 }, new string[] { "Alfa Romeo", "Maserati", "Mercedes", "Ferrari" }),
new Racer("Mike", "Hawthorn", "UK", 45, 3, new int[] { 1958 }, new string[] { "Ferrari" }),
new Racer("Phil", "Hill", "USA", 48, 3, new int[] { 1961 }, new string[] { "Ferrari" }),
new Racer("John", "Surtees", "UK", 111, 6, new int[] { 1964 }, new string[] { "Ferrari" }),
new Racer("Jim", "Clark", "UK", 72, 25, new int[] { 1963, 1965 }, new string[] { "Lotus" }),
new Racer("Jack", "Brabham", "Australia", 125, 14, new int[] { 1959, 1960, 1966 }, new string[] { "Cooper", "Brabham" }),
new Racer("Denny", "Hulme", "New Zealand", 112, 8, new int[] { 1967 }, new string[] { "Brabham" }),
new Racer("Graham", "Hill", "UK", 176, 14, new int[] { 1962, 1968 }, new string[] { "BRM", "Lotus" }),
new Racer("Jochen", "Rindt", "Austria", 60, 6, new int[] { 1970 }, new string[] { "Lotus" }),
new Racer("Jackie", "Stewart", "UK", 99, 27, new int[] { 1969, 1971, 1973 }, new string[] { "Matra", "Tyrrell" }),
new Racer("Emerson", "Fittipaldi", "Brazil", 143, 14, new int[] { 1972, 1974 }, new string[] { "Lotus", "McLaren" }),
new Racer("James", "Hunt", "UK", 91, 10, new int[] { 1976 }, new string[] { "McLaren" }),
new Racer("Mario", "Andretti", "USA", 128, 12, new int[] { 1978 }, new string[] { "Lotus" }),
new Racer("Jody", "Scheckter", "South Africa", 112, 10, new int[] { 1979 }, new string[] { "Ferrari" }),
new Racer("Alan", "Jones", "Australia", 115, 12, new int[] { 1980 }, new string[] { "Williams" }),
new Racer("Keke", "Rosberg", "Finland", 114, 5, new int[] { 1982 }, new string[] { "Williams" }),
new Racer("Niki", "Lauda", "Austria", 173, 25, new int[] { 1975, 1977, 1984 }, new string[] { "Ferrari", "McLaren" }),
new Racer("Nelson", "Piquet", "Brazil", 204, 23, new int[] { 1981, 1983, 1987 }, new string[] { "Brabham", "Williams" }),
new Racer("Ayrton", "Senna", "Brazil", 161, 41, new int[] { 1988, 1990, 1991 }, new string[] { "McLaren" }),
new Racer("Nigel", "Mansell", "UK", 187, 31, new int[] { 1992 }, new string[] { "Williams" }),
new Racer("Alain", "Prost", "France", 197, 51, new int[] { 1985, 1986, 1989, 1993 }, new string[] { "McLaren", "Williams" }),
new Racer("Damon", "Hill", "UK", 114, 22, new int[] { 1996 }, new string[] { "Williams" }),
new Racer("Jacques", "Villeneuve", "Canada", 165, 11, new int[] { 1997 }, new string[] { "Williams" }),
new Racer("Mika", "Hakkinen", "Finland", 160, 20, new int[] { 1998, 1999 }, new string[] { "McLaren" }),
new Racer("Michael", "Schumacher", "Germany", 287, 91, new int[] { 1994, 1995, 2000, 2001, 2002, 2003, 2004 }, new string[] { "Benetton", "Ferrari" }),
new Racer("Fernando", "Alonso", "Spain", 314, 32, new int[] { 2005, 2006 }, new string[] { "Renault" }),
new Racer("Kimi", "Räikkönen", "Finland", 311, 22 , new int[] { 2007 }, new string[] { "Ferrari" }),
new Racer("Lewis", "Hamilton", "UK", 249, 83, new int[] { 2008, 2014, 2015, 2017, 2018, 2019 }, new string[] { "McLaren", "Mercedes" }),
new Racer("Jenson", "Button", "UK", 306, 16, new int[] { 2009 }, new string[] { "Brawn GP" }),
new Racer("Sebastian", "Vettel", "Germany", 240, 53, new int[] { 2010, 2011, 2012, 2013 }, new string[] { "Red Bull Racing" }),
new Racer("Nico", "Rosberg", "Germany", 207, 24, new int[] { 2016 }, new string[] { "Mercedes" })
};
public static IList<Racer> GetChampions() => s_racers ?? (s_racers = InitializeRacers());
private static List<Team> s_teams;
public static IList<Team> GetConstructorChampions()
{
if (s_teams == null)
{
s_teams = new List<Team>()
{
new Team("Vanwall", 1958),
new Team("Cooper", 1959, 1960),
new Team("Ferrari", 1961, 1964, 1975, 1976, 1977, 1979, 1982, 1983, 1999, 2000, 2001, 2002, 2003, 2004, 2007, 2008),
new Team("BRM", 1962),
new Team("Lotus", 1963, 1965, 1968, 1970, 1972, 1973, 1978),
new Team("Brabham", 1966, 1967),
new Team("Matra", 1969),
new Team("Tyrrell", 1971),
new Team("McLaren", 1974, 1984, 1985, 1988, 1989, 1990, 1991, 1998),
new Team("Williams", 1980, 1981, 1986, 1987, 1992, 1993, 1994, 1996, 1997),
new Team("Benetton", 1995),
new Team("Renault", 2005, 2006 ),
new Team("Brawn GP", 2009),
new Team("Red Bull Racing", 2010, 2011, 2012, 2013),
new Team("Mercedes", 2014, 2015, 2016, 2017, 2018, 2019)
};
}
return s_teams;
}
private static List<Championship> s_championships;
public static IEnumerable<Championship> GetChampionships()
{
if (s_championships == null)
{
s_championships = new List<Championship>
{
new Championship(1950, "Nino Farina", "Juan Manuel Fangio", "Luigi Fagioli"),
new Championship(1951, "Juan Manuel Fangio", "Alberto Ascari", "Froilan Gonzalez"),
new Championship(1952, "Alberto Ascari", "Nino Farina", "Piero Taruffi"),
new Championship(1953, "Alberto Ascari", "Juan Manuel Fangio", "Nino Farina"),
new Championship(1954, "Juan Manuel Fangio", "Froilan Gonzalez", "Mike Hawthorn"),
new Championship(1955, "Juan Manuel Fangio", "Stirling Moss", "Eugenio Castellotti"),
new Championship(1956, "Juan Manuel Fangio", "Stirling Moss", "Peter Collins"),
new Championship(1957, "Juan Manuel Fangio", "Stirling Moss", "Luigi Musso" ),
new Championship(1958, "Mike Hawthorn", "Stirling Moss", "Tony Brooks"),
new Championship(1959, "Jack Brabham", "Tony Brooks", "Stirling Moss"),
new Championship(1960, "Jack Brabham", "Bruce McLaren", "Stirling Moss"),
new Championship(1961, "Phil Hill", "Wolfgang von Trips", "Stirling Moss"),
new Championship(1962, "Graham Hill", "Jim Clark", "Bruce McLaren"),
new Championship(1963, "Jim Clark", "Graham Hill", "Richie Ginther"),
new Championship(1964, "John Surtees", "Graham Hill", "Jim Clark"),
new Championship(1965, "Jim Clark", "Graham Hill", "Jackie Stewart"),
new Championship(1966, "Jack Brabham", "John Surtees", "Jochen Rindt"),
new Championship(1967, "Denny Hulme", "Jack Brabham", "Jim Clark"),
new Championship(1968, "Graham Hill", "Jackie Stewart", "Denny Hulme"),
new Championship(1969, "Jackie Stewart", "Jackie Ickx", "Bruce McLaren"),
new Championship(1970, "Jochen Rindt", "Jackie Ickx", "Clay Regazzoni"),
new Championship(1971, "Jackie Stewart", "Ronnie Peterson", "Francois Cevert"),
new Championship(1972, "Emerson Fittipaldi", "Jackie Stewart", "Denny Hulme"),
new Championship(1973, "Jackie Stewart", "Emerson Fittipaldi", "Ronnie Peterson"),
new Championship(1974, "Emerson Fittipaldi", "Clay Regazzoni", "Jody Scheckter"),
new Championship(1975, "Niki Lauda", "Emerson Fittipaldi", "Carlos Reutemann"),
new Championship(1976, "James Hunt", "Niki Lauda", "Jody Scheckter"),
new Championship(1977, "Niki Lauda", "Jody Scheckter", "Mario Andretti"),
new Championship(1978, "Mario Andretti", "Ronnie Peterson", "Carlos Reutemann"),
new Championship(1979, "Jody Scheckter", "Gilles Villeneuve", "Alan Jones"),
new Championship(1980, "Alan Jones", "Nelson Piquet", "Carlos Reutemann"),
new Championship(1981, "Nelson Piquet", "Carlos Reutemann", "Alan Jones"),
new Championship(1982, "Keke Rosberg", "Didier Pironi", "John Watson"),
new Championship(1983, "Nelson Piquet", "Alain Prost", "Rene Arnoux"),
new Championship(1984, "Niki Lauda", "Alain Prost", "Elio de Angelis"),
new Championship(1985, "Alain Prost", "Michele Alboreto", "Keke Rosberg"),
new Championship(1986, "Alain Prost", "Nigel Mansell", "Nelson Piquet"),
new Championship(1987, "Nelson Piquet", "Nigel Mansell", "Ayrton Senna"),
new Championship(1988, "Ayrton Senna", "Alain Prost", "Gerhard Berger"),
new Championship(1989, "Alain Prost", "Ayrton Senna", "Riccardo Patrese"),
new Championship(1990, "Ayrton Senna", "Alain Prost", "Nelson Piquet"),
new Championship(1991, "Ayrton Senna", "Nigel Mansell", "Riccardo Patrese"),
new Championship(1992, "Nigel Mansell", "Riccardo Patrese", "Michael Schumacher"),
new Championship(1993, "Alain Prost", "Ayrton Senna", "Damon Hill"),
new Championship(1994, "Michael Schumacher", "Damon Hill", "Gerhard Berger"),
new Championship(1995, "Michael Schumacher", "Damon Hill", "David Coulthard"),
new Championship(1996, "Damon Hill", "Jacques Villeneuve", "Michael Schumacher"),
new Championship(1997, "Jacques Villeneuve", "Heinz-Harald Frentzen", "David Coulthard"),
new Championship(1998, "Mika Hakkinen", "Michael Schumacher", "David Coulthard"),
new Championship(1999, "Mika Hakkinen", "Eddie Irvine", "Heinz-Harald Frentzen"),
new Championship(2000, "Michael Schumacher", "Mika Hakkinen", "David Coulthard"),
new Championship(2001, "Michael Schumacher", "David Coulthard", "Rubens Barrichello"),
new Championship(2002, "Michael Schumacher", "Rubens Barrichello", "Juan Pablo Montoya"),
new Championship(2003, "Michael Schumacher", "Kimi Räikkönen", "Juan Pablo Montoya"),
new Championship(2004, "Michael Schumacher", "Rubens Barrichello", "Jenson Button"),
new Championship(2005, "Fernando Alonso", "Kimi Räikkönen", "Michael Schumacher"),
new Championship(2006, "Fernando Alonso", "Michael Schumacher", "Felipe Massa"),
new Championship(2007, "Kimi Räikkönen", "Lewis Hamilton", "Fernando Alonso"),
new Championship(2008, "Lewis Hamilton", "Felipe Massa", "Kimi Räikkönen"),
new Championship(2009, "Jenson Button", "Sebastian Vettel", "Rubens Barrichello"),
new Championship(2010, "Sebastian Vettel", "Fernando Alonso", "Mark Webber"),
new Championship(2011, "Sebastian Vettel", "Jenson Button", "Mark Webber"),
new Championship(2012, "Sebastian Vettel", "Fernando Alonso", "Kimi Räikkönen"),
new Championship(2013, "Sebastian Vettel", "Fernando Alonso", "Mark Webber"),
new Championship(2014, "Lewis Hamilton", "Nico Rosberg", "Daniel Ricciardo"),
new Championship(2015, "Lewis Hamilton", "Nico Rosberg", "Sebastian Vettel"),
new Championship(2016, "Nico Rosberg", "Lewis Hamilton", "Daniel Ricciardo"),
new Championship(2017, "Lewis Hamilton", "Sebastian Vettel", "Valtteri Bottas"),
new Championship(2018, "Lewis Hamilton", "Sebastian Vettel", "Kimi Räikkönen"),
new Championship(2019, "Lewis Hamilton", "Valtteri Bottas", "Max Verstappen")
};
}
return s_championships;
}
private static IList<Racer> _moreRacers;
private static IList<Racer> GetMoreRacers()
{
if (_moreRacers == null)
{
_moreRacers = new List<Racer>();
_moreRacers.Add(new Racer("Luigi", "Fagioli", "Italy", starts: 7, wins: 1));
_moreRacers.Add(new Racer("Jose Froilan", "Gonzalez", "Argentina", starts: 26, wins: 2));
_moreRacers.Add(new Racer("Piero", "Taruffi", "Italy", starts: 18, wins: 1));
_moreRacers.Add(new Racer("Stirling", "Moss", "UK", starts: 66, wins: 16));
_moreRacers.Add(new Racer("Eugenio", "Castellotti", "Italy", starts: 14, wins: 0));
_moreRacers.Add(new Racer("Peter", "Collins", "UK", starts: 32, wins: 3));
_moreRacers.Add(new Racer("Luigi", "Musso", "Italy", starts: 24, wins: 1));
_moreRacers.Add(new Racer("Tony", "Brooks", "UK", starts: 38, wins: 6));
_moreRacers.Add(new Racer("Bruce", "McLaren", "New Zealand", starts: 100, wins: 4));
_moreRacers.Add(new Racer("Wolfgang von", "Trips", "Germany", starts: 27, wins: 2));
_moreRacers.Add(new Racer("Richie", "Ginther", "USA", starts: 52, wins: 1));
_moreRacers.Add(new Racer("Jackie", "Ickx", "Belgium", starts: 116, wins: 8));
_moreRacers.Add(new Racer("Clay", "Regazzoni", "Switzerland", starts: 132, wins: 5));
_moreRacers.Add(new Racer("Ronnie", "Peterson", "Sweden", starts: 123, wins: 10));
_moreRacers.Add(new Racer("Francois", "Cevert", "France", starts: 46, wins: 1));
_moreRacers.Add(new Racer("Carlos", "Reutemann", "Argentina", starts: 146, wins: 12));
_moreRacers.Add(new Racer("Gilles", "Villeneuve", "Canada", starts: 67, wins: 6));
_moreRacers.Add(new Racer("Didier", "Pironi", "France", starts: 70, wins: 3));
_moreRacers.Add(new Racer("John", "Watson", "UK", starts: 152, wins: 5));
_moreRacers.Add(new Racer("Rene", "Arnoux", "France", starts: 149, wins: 7));
_moreRacers.Add(new Racer("Elio", "de Angelis", "Italy", starts: 108, wins: 2));
_moreRacers.Add(new Racer("Michele", "Alboreto", "Italy", starts: 194, wins: 5));
_moreRacers.Add(new Racer("Gerhard", "Berger", "Austria", starts: 210, wins: 10));
_moreRacers.Add(new Racer("Riccardo", "Patrese", "Italy", starts: 256, wins: 6));
_moreRacers.Add(new Racer("David", "Coulthard", "UK", starts: 246, wins: 13));
_moreRacers.Add(new Racer("Heinz-Harald", "Frentzen", "Germany", starts: 156, wins: 3));
_moreRacers.Add(new Racer("Eddie", "Irvine", "UK", starts: 147, wins: 4));
_moreRacers.Add(new Racer("Rubens", "Barrichello", "Brazil", starts: 322, wins: 11));
_moreRacers.Add(new Racer("Juan Pablo", "Montoya", "Columbia", starts: 94, wins: 7));
_moreRacers.Add(new Racer("Felipe", "Massa", "Brazil", starts: 269, wins: 11));
_moreRacers.Add(new Racer("Mark", "Webber", "Australia", starts: 215, wins: 9));
_moreRacers.Add(new Racer("Daniel", "Ricciardo", "Australia", starts: 170, wins: 7));
_moreRacers.Add(new Racer("Valtteri", "Bottas", "Finland", starts: 139, wins: 7));
_moreRacers.Add(new Racer("Max", "Verstappen", "Netherlands", starts: 101, wins: 8));
}
return _moreRacers;
}
}
Championship类型。该类包含冠军年份以及该年份中获得第一名、第二名和 第三名的赛车手,对应的属性分别为Year、First> Second和Third
public class Championship
{
public Championship(int year, string first, string second, string third)
{
Year = year;
First = first;
Second = second;
Third = third;
}
public int Year { get; }
public string First { get; }
public string Second { get; }
public string Third { get; }
}
以上4个实体是接下来的查询的基础。
LINQ 查询
- 查询来自巴西的所有世界冠军,并按照夺冠次数排序
static void LINQQuery()
{
var query = from r in Formula1.GetChampions()
where r.Country == "Brazil"
orderby r.Wins descending
select r;
foreach (var r in query)
{
Console.WriteLine($"{r:A}");
}
Console.WriteLine();
}
标准查询操作符
筛选
找出贏得至少15场比赛的巴西和奥地利赛车手。传递给 where子句的表达式的结果类型应是布尔类型
public static void Filtering()
{
var racers = from r in Formula1.GetChampions()
where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")
select r;
foreach (var r in racers)
{
Console.WriteLine($"{r:A}");
}
}
用索引筛选
返回姓氏以A开头、索引为偶数的赛车手
public static void FilteringWithIndex()
{
var racers = Formula1.GetChampions()
.Where((r, index) => r.LastName.StartsWith("A") && index % 2 != 0);
foreach (var r in racers)
{
Console.WriteLine($"{r:A}");
}
}
类型筛选
为了进行基于类型的筛选,可以使用OfTypeO扩展方法。这里数组数据包含string和int对象。使用OfType() 扩展方法,把string类传送给泛型参数,就从集合中仅返回字符串
public static void TypeFiltering()
{
object[] data = { "one", 2, 3, "four", "five", 6 };
var query = data.OfType<string>();
foreach (var s in query)
{
Console.WriteLine(s);
}
}
复合的from子句
如果需要根据对象的一个成员进行筛选,而该成员本身是一个系列,就可以使用复合的from子句。
public static void CompoundFrom()
{
var ferrariDrivers = from r in Formula1.GetChampions()
from c in r.Cars
where c == "Ferrari"
orderby r.LastName
select $"{r.FirstName} {r.LastName}";
foreach (var racer in ferrariDrivers)
{
Console.WriteLine(racer);
}
}
这个查询的结果显示了驾驶法拉利的所有一级方程式冠军:
Alberto Ascari
Juan Manuel Fangio
Mike Hawthorn
Phil Hill
Niki Lauda
Kimi Raikkonen
Jody Scheckter
Michael Schumacher
John Surtees
排序
赛车手按照贏得比赛的次数进行降序排序,臝得比赛的次数用关键字选择器指定。
public static void SortDescendingWithMethods()
{
Console.WriteLine("Show all champions from Brazil ordered by wins descending");
Console.WriteLine();
var racers = Formula1.GetChampions()
.Where(r => r.Country == "Brazil")
.OrderByDescending(r => r.Wins);
foreach (var r in racers)
{
Console.WriteLine($"{r:A}");
}
}
分组
要根据一个关键字值对查询结果分组,可以使用group子句。
public static void Grouping()
{
var countries = from r in Formula1.GetChampions()
group r by r.Country into g
orderby g.Count() descending, g.Key
where g.Count() >= 2
select new
{
Country = g.Key,
Count = g.Count()
};
foreach (var item in countries)
{
Console.WriteLine($"{item.Country,-10} {item.Count}");
}
}
查询中的变量
let允许在LINQ 查询中定义变量
public static void GroupingWithVariables()
{
var countries = from r in Formula1.GetChampions()
group r by r.Country into g
let count = g.Count()
orderby count descending, g.Key
where count >= 2
select new
{
Country = g.Key,
Count = count
};
foreach (var item in countries)
{
Console.WriteLine($"{item.Country,-10} {item.Count}");
}
}
嵌套的对象分组
如果分组的对象应包含嵌套的序列,就可以改变select子句创建的匿名类型。
public static void GroupingAndNestedObjects()
{
var countries = from r in Formula1.GetChampions()
group r by r.Country into g
let count = g.Count()
orderby count descending, g.Key
where count >= 2
select new
{
Country = g.Key,
Count = count,
Racers = from r1 in g
orderby r1.LastName
select r1.FirstName + " " + r1.LastName
};
foreach (var item in countries)
{
Console.WriteLine($"{item.Country,-10} {item.Count}");
foreach (var name in item.Racers)
{
Console.Write($"{name}; ");
}
Console.WriteLine();
}
}
内连接
使用join子句可以根据特定的条件合并两个数据源,但之前要获得两个要连接的列表。
public static void InnerJoin()
{
var racers = from r in Formula1.GetChampions()
from y in r.Years
select new
{
Year = y,
Name = r.FirstName + " " + r.LastName
};
var teams = from t in Formula1.GetConstructorChampions()
from y in t.Years
select new
{
Year = y,
t.Name
};
var racersAndTeams =
(from r in racers
join t in teams on r.Year equals t.Year
orderby t.Year
select new
{
r.Year,
Champion = r.Name,
Constructor = t.Name
}).Take(10);
Console.WriteLine("Year World Champion\t Constructor Title");
foreach (var item in racersAndTeams)
{
Console.WriteLine($"{item.Year}: {item.Champion,-20} {item.Constructor}");
}
}
左外连接
左外连接用join子句和DefaultlfEmpty方法定义。如果查询 的左侧(赛车手)没有匹配的车队冠军,就使用Defeultlffimpty方法定义其右侧的默认值.
public static void LeftOuterJoin()
{
var racers = from r in Formula1.GetChampions()
from y in r.Years
select new
{
Year = y,
Name = r.FirstName + " " + r.LastName
};
var teams = from t in Formula1.GetConstructorChampions()
from y in t.Years
select new
{
Year = y,
t.Name
};
var racersAndTeams =
(from r in racers
join t in teams on r.Year equals t.Year into rt
from t in rt.DefaultIfEmpty()
orderby r.Year
select new
{
r.Year,
Champion = r.Name,
Constructor = t == null ? "no constructor championship" : t.Name
}).Take(10);
Console.WriteLine("Year Champion\t\t Constructor Title");
foreach (var item in racersAndTeams)
{
Console.WriteLine($"{item.Year}: {item.Champion,-20} {item.Constructor}");
}
}
组连接
左外连接使用了组连接和into子句。它有一部分语法与组连接相同,只不过组连接不使用Defcultlffimpty方法。 使用组连接时,可以连接两个独立的序列,对于其中一个序列中的某个元素,另一个序列中存在对应的一个项列表。
public static void GroupJoin()
{
var racers = from cs in Formula1.GetChampionships()
from r in new List<(int Year, int Position, string FirstName, string LastName)>()
{
(cs.Year, Position: 1, FirstName: cs.First.FirstName(), LastName: cs.First.LastName()),
(cs.Year, Position: 2, FirstName: cs.Second.FirstName(), LastName: cs.Second.LastName()),
(cs.Year, Position: 3, FirstName: cs.Third.FirstName(), LastName: cs.Third.LastName())
}
select r;
var q = (from r in Formula1.GetChampions()
join r2 in racers on
(
r.FirstName,
r.LastName
)
equals
(
r2.FirstName,
r2.LastName
)
into yearResults
select
(
r.FirstName,
r.LastName,
r.Wins,
r.Starts,
Results: yearResults
));
foreach (var r in q)
{
Console.WriteLine($"{r.FirstName} {r.LastName}");
foreach (var results in r.Results)
{
Console.WriteLine($"\t{results.Year} {results.Position}");
}
}
}
集合操作
扩展方法Distinct()、UnionO、Intersectf)和Except()都是集合操作
public static void SetOperations()
{
IEnumerable<Racer> racersByCar(string car) =>
from r in Formula1.GetChampions()
from c in r.Cars
where c == car
orderby r.LastName
select r;
Console.WriteLine("World champion with Ferrari and McLaren");
foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren")))
{
Console.WriteLine(racer);
}
}
合并
zip()方法允许用一个谓词函数把两个相关的序列合并为一个。
public static void ZipOperation()
{
var racerNames = from r in Formula1.GetChampions()
where r.Country == "Italy"
orderby r.Wins descending
select new
{
Name = r.FirstName + " " + r.LastName
};
var racerNamesAndStarts = from r in Formula1.GetChampions()
where r.Country == "Italy"
orderby r.Wins descending
select new
{
r.LastName,
r.Starts
};
var racers = racerNames.Zip(racerNamesAndStarts, (first, second) => first.Name + ", starts: " + second.Starts);
foreach (var r in racers)
{
Console.WriteLine(r);
}
}
分区
扩展方法IkkeO和SkipO等的分区操作可用于分页,例如,在第一个页面上只显示5个赛车手,在下一个
页面上显示接下来的5个赛车手等。
public static void Partitioning()
{
int pageSize = 5;
int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() /
(double)pageSize);
for (int page = 0; page < numberPages; page++)
{
Console.WriteLine($"Page {page}");
var racers =
(from r in Formula1.GetChampions()
orderby r.LastName, r.FirstName
select r.FirstName + " " + r.LastName).
Skip(page * pageSize).Take(pageSize);
foreach (var name in racers)
{
Console.WriteLine(name);
}
Console.WriteLine();
}
}
聚合操作
聚合操作符(如Count、Sum、Min、Max、Average和Aggregate操作符)不返回一个序列,而返回一个值。
筛选赛车手,只返 回获得冠军次数超过3次的赛车手。因为同一个查询中需要使用同一个计数超过一次,所以使用let子句定义了 一个变量 numberYears
public static void AggregateCount()
{
var query = from r in Formula1.GetChampions()
let numberYears = r.Years.Count()
where numberYears >= 3
orderby numberYears descending, r.LastName
select new
{
Name = r.FirstName + " " + r.LastName,
TimesChampion = numberYears
};
foreach (var r in query)
{
Console.WriteLine($"{r.Name} {r.TimesChampion}");
}
}
计算一个国家臝得比赛的总次数。首先根据国家对赛车手分组,再在新创建的匿名类型中,把Wins属性赋予某个国家臝得比赛的总次数
public static void AggregateSum()
{
var countries = (from c in
from r in Formula1.GetChampions()
group r by r.Country into c
select new
{
Country = c.Key,
Wins = (from r1 in c
select r1.Wins).Sum()
}
orderby c.Wins descending, c.Country
select c).Take(5);
foreach (var country in countries)
{
Console.WriteLine($"{country.Country} {country.Wins}");
}
}
转换操作
使用转换操 作符会立即执行查询,把查询结果放在数组、列表或字典中。
public static void ToList()
{
List<Racer> racers = (from r in Formula1.GetChampions()
where r.Starts > 200
orderby r.Starts descending
select r).ToList();
foreach (var racer in racers)
{
Console.WriteLine($"{racer} {racer:S}");
}
}
把返回的对象放在列表中并没有这么简单。例如,对于集合类中从赛车到赛车手的快速访问,可以使用新 类 Lookup<TKey,TElement>。
public static void ToLookup()
{
var racers = (from r in Formula1.GetChampions()
from c in r.Cars
select new
{
Car = c,
Racer = r
}).ToLookup(cr => cr.Car, cr => cr.Racer);
if (racers.Contains("Williams"))
{
foreach (var williamsRacer in racers["Williams"])
{
Console.WriteLine(williamsRacer);
}
}
}
生成操作
生成操作符Range()、Empty()fl RepeatO不是扩展方法,而是返回序列的正常静态方法。
有时需要填充一个范围的数字,此时就应使用RangeO方法。这个方法把第一个参数作为起始值,把第二个 参数作为要填充的项数
public static void GenerateRange()
{
var values = Enumerable.Range(1, 20);
foreach (var item in values)
{
Console.Write($"{item} ", item);
}
Console.WriteLine();
}
并行 LINQ
System.Linq名称空间中包含的类ParallelEnumerable可以分解查询的工作,使其分布在多个线程上。尽管 Enumerable类给IEnumerable接口定义了扩展方法,但ParallelEnumerable类的大多数扩展方法是
ParallelQuery的扩展。一个重要的例外是AsParallel()方法,它扩展了 IEnumerable接口, 返回ParallelQuery类,所以正常的集合类可以以并行方式查询。
并行查询
为了说明PLINQ (Parallel LINQ,并行LINQ),需要一个大型集合。对于可以放在CPU的缓存中的小集合,
并行LINQ看不出效果。在下面的代码中,用随机值填充一个大型的int集合
static IList<int> SampleData()
{
const int arraySize = 50000000;
var r = new Random();
return Enumerable.Range(0, arraySize).Select(x => r.Next(140)).ToList();
}
现在可以使用LINQ查询筛选数据,进行一些计算,获取所筛选数据的平均数。该查询用where子句定义
了一个筛选器,仅汇总对应值小于20的项,接着调用聚合函数Sum()方法。与前面的LINQ查询的唯一区别是, 这次调用了 AsParallel()方法。
static void LinqQuery(IEnumerable<int> data)
{
Console.WriteLine(nameof(LinqQuery));
var res = (from x in data.AsParallel()
where Math.Log(x) < 4
select x).Average();
Console.WriteLine($"result from {nameof(LinqQuery)}: {res}");
Console.WriteLine();
}
运行这行代码会启动任务管理器,这样就可以看出系统的所有CPU都在忙碌。如果删除AsParallelO方法, 就不可能使用多个CPU。当然,如果系统上没有多个CPU,就不会看到并行版本带来的改进。
分区器
AsParallel()方法不仅扩展了IEnumerable接口,还扩展了Partitioned。通过它,可以影响要创建的分区。 Partitioner类用System.Collection.Concurrent名称空间定义,并且有不同的变体。Create()方法接受实现了 IList类的数组或对象。根据这一点,以及Boolean类型的参数loadBalance和该方法的一些重载 版本,会返回一个不同的Partitioner类型。对于数组,使用派生自抽象OrderablePartitioner
的 DynamicPartitionerForArray类和 StaticPartitionerForArray类。
static void UseAPartitioner(IList<int> data)
{
Console.WriteLine(nameof(UseAPartitioner));
var res = (from x in Partitioner.Create(data, loadBalance: true).AsParallel()
where Math.Log(x) < 4
select x).Average();
Console.WriteLine($"result from {nameof(UseAPartitioner)}: {res}");
Console.WriteLine();
}
也可以调用 WithExecutionModeO和WithDegreeOfParalielismO方法来影响并行机制。对于WithExecutionMode() 方法,可以传递ParallelExecutionMode的一个Defeult值或者ForceParallelism值。默认情况下,并行LINQ避免使 用系统开销很高的并行机制。对于WithDegreeOffarallelism()方法,可以传递一个整数值,以指定应并行运行的 最大任务数。查询不应使用全部CPU,这个方法会很有用。
取消
.NET提供了一种标准方式,来取消长时间运行的任务,这也适用于并行LINQ。要取消长时间运行的查询,可以给查询添加WithCancellationO方法,并传递一个CancellationToken令牌作为 参数CancellationToken令牌从CancellationTokenSource类中创建。该查询在单独的线程中运行,在该线程中,捕 获一个OperationCanceledException类型的异常。如果取消了查询,就触发这个异常。在主线程中,调用 CancellationTokenSource 类的 Cancel()方法可以取消任务.
static void UseCancellation(IEnumerable<int> data)
{
Console.WriteLine(nameof(UseCancellation));
var cts = new CancellationTokenSource();
Task.Run(() =>
{
try
{
var res = (from x in data.AsParallel().WithCancellation(cts.Token)
where Math.Log(x) < 4
select x).Average();
Console.WriteLine($"query finished, sum: {res}");
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
});
Console.WriteLine("query started");
Console.Write("cancel? ");
string input = Console.ReadLine();
if (input.ToLower().Equals("y"))
{
cts.Cancel();
}
Console.WriteLine();
}
表达式树
在LINQ to Objects中,扩展方法需要将一个委托类型作为参数,这样就可以将lambda表达式赋予参数。
lambda表达式也可以赋予Expression类型的参数。C#编译器根据类型给lambda表达式定义不同的行为。如 果类型是Expression,编译器就从lambda表达式中创建一个表达式树,并存储在程序集中。这样,就可以 在运行期间分析表达式树,并进行优化,以便于查询数据源。
class Program
{
static void Main()
{
Expression<Func<Racer, bool>> expression = r => r.Country == "Brazil" && r.Wins > 6;
DisplayTree(0, "Lambda", expression);
}
static void DisplayTree(int indent, string message, Expression expression)
{
string output = $"{ string.Empty.PadLeft(indent, '>')} {message} ! NodeType: {expression.NodeType}; Expr: {expression}";
indent++;
switch (expression.NodeType)
{
case ExpressionType.Lambda:
Console.WriteLine(output);
LambdaExpression lambdaExpr = (LambdaExpression)expression;
foreach (var parameter in lambdaExpr.Parameters)
{
DisplayTree(indent, "Parameter", parameter);
}
DisplayTree(indent, "Body", lambdaExpr.Body);
break;
case ExpressionType.Constant:
ConstantExpression constExpr = (ConstantExpression)expression;
Console.WriteLine($"{output} Const Value: {constExpr.Value}");
break;
case ExpressionType.Parameter:
ParameterExpression paramExpr = (ParameterExpression)expression;
Console.WriteLine($"{output} Param Type: {paramExpr.Type.Name}");
break;
case ExpressionType.Equal:
case ExpressionType.AndAlso:
case ExpressionType.GreaterThan:
BinaryExpression binExpr = (BinaryExpression)expression;
if (binExpr.Method != null)
{
Console.WriteLine($"{output} Method: {binExpr.Method.Name}");
}
else
{
Console.WriteLine(output);
}
DisplayTree(indent, "Left", binExpr.Left);
DisplayTree(indent, "Right", binExpr.Right);
break;
case ExpressionType.MemberAccess:
MemberExpression memberExpr = (MemberExpression)expression;
Console.WriteLine($"{output} Member Name: {memberExpr.Member.Name}, Type: {memberExpr.Type.Name}");
DisplayTree(indent, "Member Expr", memberExpr.Expression);
break;
default:
Console.WriteLine();
Console.WriteLine($"{expression.NodeType} {expression.Type.Name}");
break;
}
}
}