();
11 | }
12 | }
13 |
14 | public class Benchmarks
15 | {
16 | readonly LargeImmutableStruct immutableStruct = new LargeImmutableStruct(1.1, 2.2);
17 | readonly LargeMutableStruct mutableStruct = new LargeMutableStruct(1.1, 2.2);
18 |
19 | [Benchmark]
20 | public void ImmutableAddByType() => AddByType(immutableStruct);
21 |
22 | [Benchmark]
23 | public void ImmutableAddByRefType() => AddByRefType(in immutableStruct);
24 |
25 | [Benchmark]
26 | public void MutableAddByType() => AddByType(mutableStruct);
27 |
28 | [Benchmark]
29 | public void MutableAddByRefType() => AddByRefType(in mutableStruct);
30 |
31 | double AddByType(LargeImmutableStruct s) => s.X + s.Y;
32 | double AddByRefType(in LargeImmutableStruct s) => s.X + s.Y;
33 | double AddByType(LargeMutableStruct s) => s.X + s.Y;
34 | double AddByRefType(in LargeMutableStruct s) => s.X + s.Y;
35 | }
36 | }
--------------------------------------------------------------------------------
/7 - Advanced Task Composition/README.md:
--------------------------------------------------------------------------------
1 | # Afternoon Async / Await lab
2 |
3 | ## Add error reporting.
4 |
5 | The first lab did nothing with errors. Let's fix that in two ways:
6 |
7 | 1. Validate args and state with a synchronous Task-returning method.
8 | 1. Report errors correctly with proper await or processing async errors.
9 |
10 | That means adding the API errors collection to the returned data.
11 | Throw when that data comes back bad. Each error has a message, locations array.
12 |
13 | ## Interface / API critique
14 |
15 | How would you create a class hierarchy for these APIs? What operations should be synchronous? What should be asynchronous?
16 |
17 | How does that shape construction, properties, and other elements of APIs?
18 |
19 | Some thoughts:
20 | Are async APIs factories that *produce* results as new objects?
21 | Should async APIs perform *actions* that modifying existing objects?
22 |
23 | ## Progress and cancellation.
24 |
25 | As a demo, show the page cursor features in the GitHub explorer web page. Once you've done that, introduce the lab:
26 |
27 | Now, we want all the open issues (newest first) in those respositories. Get them 25 per page.
28 |
29 | Add a progress report for "retrieved M of N issues".
30 | Support cancellation at the end of each response.
31 |
32 | If short of time, just add one overload that supports both cancellation and progress.
33 | If enough time, add all permutations.
34 |
--------------------------------------------------------------------------------
/4 - Memory And Performance/Lab/Benchmark/Benchmark/BenchmarkDotNet.Artifacts/results/Benchmark.Benchmarks-report.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Benchmark.Benchmarks
6 |
7 |
13 |
14 |
15 |
16 | BenchmarkDotNet=v0.11.3, OS=macOS Mojave 10.14.3 (18D42) [Darwin 18.2.0]
17 | Intel Core i7-7920HQ CPU 3.10GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
18 | .NET Core SDK=3.0.100-preview-010177
19 | [Host] : .NET Core ? (CoreCLR 4.6.0.0, CoreFX 4.6.26614.01), 64bit RyuJIT
20 | DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT
21 |
22 |
23 |
24 |
25 | | Method | Mean | Error | StdDev | Median |
26 |
27 | | ImmutableAddByType | 0.0191 ns | 0.0224 ns | 0.0210 ns | 0.0110 ns |
28 |
| ImmutableAddByRefType | 0.3007 ns | 0.0381 ns | 0.0648 ns | 0.2939 ns |
29 |
| MutableAddByType | 0.0105 ns | 0.0198 ns | 0.0186 ns | 0.0000 ns |
30 |
| MutableAddByRefType | 17.6355 ns | 0.3840 ns | 0.4856 ns | 17.6148 ns |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/3 - Exceptions and Errors/README.md:
--------------------------------------------------------------------------------
1 | # Exceptions Demo
2 |
3 | First, walk through the MakeChange routine at a conceptual level. Do not show the code. It sounds simple: look at the idea where you make change from the largest bills (since those are the ones people likely give you.) When you run out of larger bills, start using smaller ones. The functionality is simple and clearly defined.
4 |
5 | There are two error conditions: One is when the amount paid doesn't cover the cost of the items. The other is when the till doesn't have enough small bills to make change. Once again, stress the simplicity.
6 |
7 | It looks simple. Now, without looking at the implementation of the till class, show the test code in program.cs.
8 |
9 | Run the simulation with a small set of data, like you would in a unit test. It looks great. Now, change the number of iterations and try again. Point out the runtime problem: State has been mutated when there have been error conditions. If you run this over a longer period of time, it will always fail eventually.
10 |
11 | Why did this happen? Well, the code mutated state before it encountered an error condition.
12 |
13 | Key point: This is a small simulation that demonstrates a common problem in large code bases: Unless unit tests are carefully constructed, exceptions may not always occur in your test environments. It's only under production stress that these appear. Then, data errors happen with live customer data. It gets very expensive and becomes a data recovery issue.
14 |
15 | ## The lab
16 |
17 | Now, show the code for the make change method. The lab is to fix the code *so that it still throws exceptions in these error conditions*, but recovery is safe. That means the state of the till cannot change when the purchase can't be completed.
18 |
19 | There are a number of ways to implement the change, but all share some common themes discussed in the "practices" slide.
20 |
--------------------------------------------------------------------------------
/4 - Memory And Performance/Lab/Benchmark/Benchmark/BenchmarkDotNet.Artifacts/results/Benchmark.Benchmarks-report.csv:
--------------------------------------------------------------------------------
1 | Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,Platform,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Median
2 | ImmutableAddByType,Default,False,Default,Default,Default,Default,Default,Default,00000000,Empty,RyuJit,X64,Core,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,0.0191 ns,0.0224 ns,0.0210 ns,0.0110 ns
3 | ImmutableAddByRefType,Default,False,Default,Default,Default,Default,Default,Default,00000000,Empty,RyuJit,X64,Core,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,0.3007 ns,0.0381 ns,0.0648 ns,0.2939 ns
4 | MutableAddByType,Default,False,Default,Default,Default,Default,Default,Default,00000000,Empty,RyuJit,X64,Core,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,0.0105 ns,0.0198 ns,0.0186 ns,0.0000 ns
5 | MutableAddByRefType,Default,False,Default,Default,Default,Default,Default,Default,00000000,Empty,RyuJit,X64,Core,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,1,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,17.6355 ns,0.3840 ns,0.4856 ns,17.6148 ns
6 |
--------------------------------------------------------------------------------
/4 - Memory And Performance/README.md:
--------------------------------------------------------------------------------
1 | # In, out, ref, and benchmaks
2 |
3 | ## Steps to Execute Benchmark
4 |
5 | ### 1. Publish The Project
6 |
7 | Run the following command to create a self contained executable
8 |
9 | - Windows 64-bit OS
10 |
11 | ```console
12 | dotnet publish -c Release --self-contained -r win10-x64
13 | ```
14 |
15 | - Windows 32-bit OS
16 |
17 | ```console
18 | dotnet publish -c Release --self-contained -r win10-x86
19 | ```
20 |
21 | - macOS
22 |
23 | ```console
24 | dotnet publish ./Benchmark.csproj -c Release --self-contained -r osx-x64
25 | ```
26 |
27 | ### 2. Run the Benchmark
28 |
29 | Run from the following command in the project directory
30 |
31 | - Windows 64-bit OS
32 |
33 | ```console
34 | ./bin/Release/netcoreapp2.0/win10-x64/benchmark.exe
35 | ```
36 |
37 | - Windows 32-bit OS
38 |
39 | ```console
40 | ./bin/Release/netcoreapp2.0/win10-x32/benchmark.exe
41 | ```
42 |
43 | - macOS
44 |
45 | ```console
46 | ./bin/Release/netcoreapp2.0/osx-x64/Benchmark
47 | ```
48 |
49 | ## Sample Benchmark Results
50 |
51 | | Method | Mean | Error | StdDev | Median |
52 | |--------|------|-------|--------|--------|
53 | | ImmutableAddByType | 0.0191 ns | 0.0224 ns | 0.0210 ns | 0.0110 ns |
54 | | ImmutableAddByRefType | 0.3007 ns | 0.0381 ns | 0.0648 ns | 0.2939 ns |
55 | | MutableAddByType | 0.0105 ns | 0.0198 ns | 0.0186 ns | 0.0000 ns |
56 | | MutableAddByRefType | 17.6355 ns | 0.3840 ns | 0.4856 ns | 17.6148 ns |
57 |
58 | ## Lab Explanation
59 |
60 | In this lab, we will explore the following:
61 |
62 | - pass by 'ref'
63 | - pass uninitialized struct by 'out'
64 |
65 | Explore different sizes. For the last one, compare with
66 | members of your group because different machines may
67 | have different characteristics.
68 |
69 | Another question to explore if changing between 64 bit
70 | and 32 bit and independent makes a difference. How about
71 | .NET Framework and .NET Core?
72 |
73 | Consider making a ref struct.
74 |
75 | Consider adding interface implementation to the benchmark. How does that change the performance on structs, readonly structs, and ref struct (trick question, ref structs can't add interface implementation)
76 |
77 | ## Presenter Notes
78 |
79 | Run the benchmark program. explain how
80 | benchmark.net works. It's OSS, its on NuGet and it's a great
81 | tool to measure performance on low-level algorithms.
82 |
83 | As written, this compares four permutations:
84 | mutable struct, passed by value,
85 | mutable struct, pased by readonly ref,
86 | immutable struct, passed by value,
87 | immutable struct, passed by readonly ref.
88 |
89 | There are other permutations that apply and can help understand
90 | the nuances of different design choices.
--------------------------------------------------------------------------------
/2 - Tuples and Patterns/README.md:
--------------------------------------------------------------------------------
1 | # Using Tuples
2 |
3 | ## Tuple Demo
4 |
5 | Show the generator class. Explain that this simulates and IoT sensor that returns
6 | pairs of numbers that should be formed into (x,y) bits.
7 |
8 | First part: create GetPoints() to return named tuples for X, Y
9 |
10 | Next, compote the distance between the points using tuples for dx, dy
11 |
12 | Add a SameQuadrant checking the sign of both points.
13 |
14 | ## Discussion: Rules for classes, structs, Tuples and anonymous types
15 |
16 | Ask the question: When should you pick tuples as your design choice?
17 |
18 | prediction that will lead to a bit fo a loss, without good answers. That's because tuples don't solve an *obvious* problem.
19 |
20 | Turn the discussion around: Ask when should you choose a class? When should you choose a struct? What features of classes or structs make them suitable for your designs?
21 |
22 | prediction: this gets to classes solving the problem of behavior + data. They include inheritance, interface implementation, member methods etc.
23 | Structs include member methods, can implement interfaces (yes, boxing), but no inheritance.
24 |
25 | Tuples have *none of those features*. They are a 'better' property bag. Let's compare that with our demo: So fare, the point has no behavior. Stated another way, that means you don't need member methods. They have no inheritance, they don't implement arbitrary interfaces.
26 |
27 | Consider features and patterns that would not be there if Tuples had existed since C# 1.0:
28 | - The try / out pattern
29 | - Others? (I don't know, but maybe we get good suggestions).
30 |
31 | ## Patterns bal
32 |
33 | Extend the point sample to read JSON that could be points, or could be (dx, dy) directions.
34 |
35 | Then, start computing the new location based on reading that kind of information.
36 |
37 | On the patterns: Point out that JSON data means it doesn't always have a structure.
38 | Deserialization could fail if properties are not prese3nt. By using *patterns* instead
39 | of a type, you continue to have the flexibility you need for extending the behavior without doing as much type plumbing when fields change (possibly not under your control.)
40 |
41 | Show a pattern where a switch looks at some data in an object to determine what to do.
42 | Explain that you could extend that so this code that retrieves points or deltas could generate a sequence of points. Have them do that as a lab. (Point out that the JSON is as well formed as we'd like. Sometimes it's "X", "Y". Sometimes it "x", "y", and so on. Patterns can manage that too.)
43 |
44 | Drive a discussion from this:
45 |
46 | When do the fields of a type change during development?
47 |
48 | Will patterns make it more resilient? Or is it lazy? What are the tradeoffs?
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C# Workshop
2 |
3 | [NDC London 2019](https://ndc-london.com/workshop/become-a-better-c-programmer-more-value-more-expressions-no-waiting/)
4 |
5 | Hosted by [Bill Wagner](https://ndc-london.com/speaker/bill-wagner)
6 |
7 | ### Become a better C# programmer: more Value, more Expressions, no Waiting
8 |
9 | Over the past few releases, C# has added features that greatly improve productivity.
10 |
11 | In this workshop, you’ll learn scenarios where these new features make you more productive, and improve the clarity of your code. You’ll get a guided tour through the async and await wilderness. You'll start with basic uses where async and await work like magic. From there, you'll learn common practices and how async tasks compose. You'll dive into enough of the implementation details to understand how to apply async practices. After working through these different practices, you'll emerge from the wilderness understanding how to write clear, correct and safe async code. From there, you'll work through everyday scenarios where modern C# frees itself from the shackles of history. You'll see new ways to work with text, new expressions for control flow, and new ways to bend the type system to your will.
12 | You’ll learn:
13 |
14 | - [x] Getting started with the basics of async and await
15 | - [x] Distinguish asynchronous programming and parallel programming
16 | - [x] Compose asynchronous method calls throughout your code
17 | - [x] Understand the pitfalls of async void
18 | - [x] Designing async APIs
19 | - [x] Advanced async and Task based programming
20 | - [x] How string interpolation makes formatting strings much easier and clearer.
21 | - [x] How to create compound assignments and comparisons using Tuples and Deconstruction
22 | - [x] How to simplify iterators and async error handling using local functions
23 | - [x] How to simplify error reporting using throw expressions in expression bodied members.
24 | - [x] Write more performant code using value types safe pass-by-reference
25 | - [x] How to use Pattern Matching to create algorithms that extend existing types
26 |
27 | ## Computer Setup:
28 |
29 | Attendees will need to bring a laptop with one of the following setups:
30 |
31 | - Windows: Laptop running Visual Studio 2017, or Visual Studio Code
32 | - Mac: Laptop running Visual Studio Code
33 | - Linux: Laptop running Visual Studio Code
34 |
35 | ## Recommendations for Exercises
36 |
37 | 1. We're here to learn, not to win arguments. Some exercises will spark debate. *Listen and learn*. *what* answer your group finds is much less important than *why* you chose that answer. A related point is that you will learn from other groups' opinions, and teach by explaining your group's opinions.
38 | 2. Every person should be heard.
39 | 3. Change roles often in pair programming exercises.
40 |
--------------------------------------------------------------------------------
/5 - One Abstraction/Labs/Started/AsyncBreakfast/AsyncBreakfast/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace AsyncBreakfast
5 | {
6 | class Program
7 | {
8 | static void Main(string[] args)
9 | {
10 | Coffee cup = PourCoffee();
11 | Console.WriteLine("Coffee is ready");
12 |
13 | Egg eggs = FryEggs(2);
14 | Console.WriteLine("Eggs are ready");
15 |
16 | Bacon bacon = FryBacon(3);
17 | Console.WriteLine("Bacon is ready");
18 |
19 | Toast toast = ToastBread(2);
20 |
21 | ApplyButter(toast);
22 | ApplyJam(toast);
23 |
24 | Console.WriteLine("Toast is ready");
25 |
26 | Juice oj = PourOJ();
27 | Console.WriteLine("OJ is ready");
28 |
29 | Console.WriteLine("Breakfast is ready!");
30 | }
31 |
32 | private static Juice PourOJ()
33 | {
34 | Console.WriteLine("Pouring Orange Juice");
35 | return new Juice();
36 | }
37 |
38 | private static void ApplyJam(Toast toast) => Console.WriteLine("Putting jam on the toast");
39 |
40 | private static void ApplyButter(Toast toast) => Console.WriteLine("Putting butter on the toast");
41 |
42 | private static Toast ToastBread(int slices)
43 | {
44 | for (int slice = 0; slice < slices; slice++)
45 | Console.WriteLine("Putting a slice of bread in the toaster");
46 |
47 | Console.WriteLine("Start toasting...");
48 |
49 | Task.Delay(3000).Wait();
50 |
51 | Console.WriteLine("Remove toast from toaster");
52 |
53 | return new Toast();
54 | }
55 |
56 | private static Bacon FryBacon(int slices)
57 | {
58 | Console.WriteLine($"Putting {slices} of bacon in the pan");
59 |
60 | Console.WriteLine("Cooking first side of bacon...");
61 | Task.Delay(3000).Wait();
62 |
63 | for (int slice = 0; slice < slices; slice++)
64 | Console.WriteLine("Flipping a slice of bacon");
65 |
66 | Console.WriteLine("Cooking the second side of bacon...");
67 | Task.Delay(3000).Wait();
68 |
69 | Console.WriteLine("Putting bacon on plate");
70 |
71 | return new Bacon();
72 | }
73 |
74 | private static Egg FryEggs(int howMany)
75 | {
76 | Console.WriteLine("Warming the egg pan...");
77 | Task.Delay(3000).Wait();
78 |
79 | Console.WriteLine($"Cracking {howMany} eggs");
80 |
81 | Console.WriteLine("Cooking the eggs ...");
82 | Task.Delay(3000).Wait();
83 |
84 | Console.WriteLine("Putting eggs on plate");
85 |
86 | return new Egg();
87 | }
88 |
89 | private static Coffee PourCoffee()
90 | {
91 | Console.WriteLine("Pouring coffee");
92 | return new Coffee();
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/5 - One Abstraction/Labs/First Checkpoint/AsyncBreakfast/AsyncBreakfast/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace AsyncBreakfast
5 | {
6 | class Program
7 | {
8 | static async Task Main(string[] args)
9 | {
10 | Coffee cup = PourCoffee();
11 | Console.WriteLine("Coffee is ready");
12 |
13 | Egg eggs = await FryEggs(2);
14 | Console.WriteLine("Eggs are ready");
15 |
16 | Bacon bacon = await FryBacon(3);
17 | Console.WriteLine("Bacon is ready");
18 |
19 | Toast toast = await ToastBread(2);
20 |
21 | ApplyButter(toast);
22 | ApplyJam(toast);
23 |
24 | Console.WriteLine("Toast is ready");
25 |
26 | Juice oj = PourOJ();
27 |
28 | Console.WriteLine("OJ is ready");
29 |
30 | Console.WriteLine("Breakfast is ready!");
31 | }
32 |
33 | private static Juice PourOJ()
34 | {
35 | Console.WriteLine("Pouring Orange Juice");
36 | return new Juice();
37 | }
38 |
39 | private static void ApplyJam(Toast toast) => Console.WriteLine("Putting jam on the toast");
40 |
41 | private static void ApplyButter(Toast toast) => Console.WriteLine("Putting butter on the toast");
42 |
43 | private static async Task ToastBread(int slices)
44 | {
45 | for (int slice = 0; slice < slices; slice++)
46 | Console.WriteLine("Putting a slice of bread in the toaster");
47 |
48 | Console.WriteLine("Start toasting...");
49 | await Task.Delay(3000);
50 |
51 | Console.WriteLine("Removing toast from toaster");
52 |
53 | return new Toast();
54 | }
55 |
56 | private static async Task FryBacon(int slices)
57 | {
58 | Console.WriteLine($"Putting {slices} of bacon in the pan");
59 | Console.WriteLine("Cooking first side of bacon...");
60 | await Task.Delay(3000);
61 |
62 | for (int slice = 0; slice < slices; slice++)
63 | Console.WriteLine("Flipping a slice of bacon");
64 |
65 | Console.WriteLine("Cooking the second side of bacon...");
66 | await Task.Delay(3000);
67 |
68 | Console.WriteLine("Putting bacon on plate");
69 |
70 | return new Bacon();
71 | }
72 |
73 | private static async Task FryEggs(int howMany)
74 | {
75 | Console.WriteLine("Warming the egg pan...");
76 | await Task.Delay(3000);
77 |
78 | Console.WriteLine($"Cracking {howMany} eggs");
79 |
80 | Console.WriteLine("Cooking the eggs ...");
81 | await Task.Delay(3000);
82 |
83 | Console.WriteLine("Putting eggs on plate");
84 |
85 | return new Egg();
86 | }
87 |
88 | private static Coffee PourCoffee()
89 | {
90 | Console.WriteLine("Pouring coffee");
91 | return new Coffee();
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/2 - Tuples and Patterns/Lab/Completed/TuplesAndPatterns/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json.Linq;
4 | using System.Linq;
5 |
6 | namespace TuplesAndPatterns
7 | {
8 | class Program
9 | {
10 | // Use a simple JSON object that cotnains points (x,y) or distance (dX, dY),
11 | // Use pattern matching to build the IEnumerable
12 | // It's simple to explain and simple to make it work.
13 | private const string jsonText =
14 | @"{
15 | path: [
16 | {
17 | X: 20,
18 | Y: 12
19 | },
20 | {
21 | dx: -23,
22 | dy: -5
23 | },
24 | {
25 | x: 5,
26 | y: 12
27 | },
28 | {
29 | deltaX: 10,
30 | deltaY: 5
31 | }
32 | ]
33 | }";
34 |
35 | static void Main(string[] args)
36 | {
37 | var points = GeneratePoints(50);
38 |
39 | var currentPoint = (X: 0.0, Y: 0.0);
40 |
41 | foreach (var point in points)
42 | {
43 | var xyDistance = (x: point.X - currentPoint.X, y: point.Y - currentPoint.Y);
44 |
45 | var distance = Math.Sqrt(xyDistance.x * xyDistance.x + xyDistance.y * xyDistance.y);
46 |
47 | Console.Write($"The distance from {currentPoint} to {point} is approximately {Math.Round(distance, 2)}");
48 |
49 | if (ArePointsInSameQuadrant(currentPoint, point))
50 | Console.WriteLine(" and they are in the same quadrant.");
51 | else
52 | Console.WriteLine(" and they are in different quadrants.");
53 |
54 | currentPoint = point;
55 |
56 | Console.WriteLine();
57 | }
58 |
59 | points = GetParsedJSONPoints(jsonText);
60 |
61 | Console.WriteLine("Parsed JSON Output");
62 | foreach (var point in points)
63 | Console.WriteLine(point);
64 | }
65 |
66 | static bool ArePointsInSameQuadrant((double X, double Y) left, (double X, double Y) right) =>
67 | (Math.Sign(left.X), Math.Sign(left.Y)) == (Math.Sign(right.X), Math.Sign(right.Y));
68 |
69 | static IEnumerable<(double X, double Y)> GetParsedJSONPoints(string json)
70 | {
71 | JObject data = JObject.Parse(json);
72 |
73 | JArray trip = (JArray)data["path"];
74 |
75 | foreach (JObject obj in trip)
76 | {
77 | yield return ((double)obj.Values().First(), (double)obj.Values().Skip(1).First());
78 | }
79 | }
80 |
81 | static IEnumerable<(double X, double Y)> GeneratePoints(int numberOfPointsToGenerate)
82 | {
83 | var generator = new ConnectedSensor();
84 |
85 | var pointsEnumerator = generator.ReadData(numberOfPointsToGenerate * 2).GetEnumerator();
86 |
87 | while (pointsEnumerator.MoveNext())
88 | {
89 | var x = pointsEnumerator.Current;
90 |
91 | pointsEnumerator.MoveNext();
92 |
93 | var y = pointsEnumerator.Current;
94 |
95 | yield return (x, y);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/2 - Tuples and Patterns/Lab/Started/TuplesAndPatterns/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json.Linq;
4 |
5 | namespace TuplesAndPatterns
6 | {
7 | class Program
8 | {
9 | // Use a simple JSON object that contains points (x,y) or distance (dX, dY),
10 | // Use pattern matching to build the IEnumerable
11 | // It's simple to explain and simple to make it work.
12 |
13 | private const string jsonText =
14 | @"{
15 | path: [
16 | {
17 | X: 20,
18 | Y: 12
19 | },
20 | {
21 | dx: -23,
22 | dy: -5
23 | },
24 | {
25 | x: 5,
26 | y: 12
27 | },
28 | {
29 | deltaX: 10,
30 | deltaY: 5
31 | }
32 | ]
33 | }";
34 |
35 | static void Main(string[] args)
36 | {
37 | var points = GeneratePoints(50);
38 |
39 | var currentPoint = (X: 0.0, Y: 0.0);
40 |
41 | foreach (var point in points)
42 | {
43 | var xyDistance = (x: point.X - currentPoint.X, y: point.Y - currentPoint.Y);
44 |
45 | var distance = Math.Sqrt(xyDistance.x * xyDistance.x + xyDistance.y * xyDistance.y);
46 |
47 | Console.Write($"The distance from {currentPoint} to {point} is approximately {Math.Round(distance, 2)}");
48 |
49 | if (ArePointsInSameQuadrant(currentPoint, point))
50 | Console.WriteLine(" and they are in the same quadrant.");
51 | else
52 | Console.WriteLine(" and they are in different quadrants.");
53 |
54 | currentPoint = point;
55 |
56 | Console.WriteLine();
57 | }
58 |
59 | points = GetParsedJSONPoints(jsonText);
60 |
61 | Console.WriteLine("Parsed JSON Output");
62 | foreach (var point in points)
63 | Console.WriteLine(point);
64 | }
65 |
66 | static IEnumerable<(double X, double Y)> GetParsedJSONPoints(string json)
67 | {
68 | JObject data = JObject.Parse(json);
69 |
70 | JArray trip = (JArray)data["path"];
71 |
72 | foreach (JObject obj in trip)
73 | {
74 | // TODO: Extend this to use pattern matching to return the next point.
75 | }
76 |
77 | throw new NotImplementedException("Delete this Exception once TODO is implemented");
78 | }
79 |
80 | static bool ArePointsInSameQuadrant((double X, double Y) left, (double X, double Y) right) =>
81 | (Math.Sign(left.X), Math.Sign(left.Y)) == (Math.Sign(right.X), Math.Sign(right.Y));
82 |
83 | static IEnumerable<(double X, double Y)> GeneratePoints(int numberOfPointsToGenerate)
84 | {
85 | var generator = new ConnectedSensor();
86 |
87 | var pointsEnumerator = generator.ReadData(numberOfPointsToGenerate * 2).GetEnumerator();
88 |
89 | while (pointsEnumerator.MoveNext())
90 | {
91 | var x = pointsEnumerator.Current;
92 |
93 | pointsEnumerator.MoveNext();
94 |
95 | var y = pointsEnumerator.Current;
96 |
97 | yield return (x, y);
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/5 - One Abstraction/Labs/Completed/AsyncBreakfast/AsyncBreakfast/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace AsyncBreakfast
5 | {
6 | class Program
7 | {
8 | static async Task Main(string[] args)
9 | {
10 | Coffee cup = PourCoffee();
11 | Console.WriteLine("Coffee is ready");
12 |
13 | var eggsTask = FryEggsAsync(2);
14 | var baconTask = FryBaconAsync(3);
15 | var toastTask = makeToastWithButterAndJamAsync(2);
16 |
17 | var eggs = await eggsTask;
18 | Console.WriteLine("Eggs are ready");
19 |
20 | var bacon = await baconTask;
21 | Console.WriteLine("Bacon is ready");
22 |
23 | var toast = await toastTask;
24 | Console.WriteLine("Toast is ready");
25 |
26 | Juice oj = PourOJ();
27 | Console.WriteLine("OJ is ready");
28 |
29 | Console.WriteLine("Breakfast is ready!");
30 |
31 | async Task makeToastWithButterAndJamAsync(int number)
32 | {
33 | var plainToast = await ToastBreadAsync(number);
34 |
35 | ApplyButter(plainToast);
36 | ApplyJam(plainToast);
37 |
38 | return plainToast;
39 | }
40 | }
41 |
42 | private static Juice PourOJ()
43 | {
44 | Console.WriteLine("Pouring Orange Juice");
45 | return new Juice();
46 | }
47 |
48 | private static void ApplyJam(Toast toast) => Console.WriteLine("Putting jam on the toast");
49 |
50 | private static void ApplyButter(Toast toast) => Console.WriteLine("Putting butter on the toast");
51 |
52 | private static async Task ToastBreadAsync(int slices)
53 | {
54 | for (int slice = 0; slice < slices; slice++)
55 | Console.WriteLine("Putting a slice of bread in the toaster");
56 |
57 | Console.WriteLine("Start toasting...");
58 | await Task.Delay(3000);
59 |
60 | Console.WriteLine("Remove toast from toaster");
61 |
62 | return new Toast();
63 | }
64 |
65 | private static async Task FryBaconAsync(int slices)
66 | {
67 | Console.WriteLine($"Putting {slices} of bacon in the pan");
68 |
69 | Console.WriteLine("Cooking first side of bacon...");
70 | await Task.Delay(3000);
71 |
72 | for (int slice = 0; slice < slices; slice++)
73 | Console.WriteLine("Flipping a slice of bacon");
74 |
75 | Console.WriteLine("Cooking the second side of bacon...");
76 | await Task.Delay(3000);
77 |
78 | Console.WriteLine("Putting bacon on plate");
79 | return new Bacon();
80 | }
81 |
82 | private static async Task FryEggsAsync(int howMany)
83 | {
84 | Console.WriteLine("Warming the egg pan...");
85 | await Task.Delay(3000);
86 |
87 | Console.WriteLine($"Cracking {howMany} eggs");
88 |
89 | Console.WriteLine("Cooking the eggs ...");
90 | await Task.Delay(3000);
91 |
92 | Console.WriteLine("Putting eggs on plate");
93 | return new Egg();
94 | }
95 |
96 | private static Coffee PourCoffee()
97 | {
98 | Console.WriteLine("Pouring coffee");
99 | return new Coffee();
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/6 - Task Composition/Lab/Started/IssuePRreport/IssuePRreport/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Linq;
6 |
7 | namespace GitHubActivityReport
8 | {
9 | class GraphQLRequest
10 | {
11 | [JsonProperty("query")]
12 | public string Query { get; set; }
13 |
14 | [JsonProperty("variables")]
15 | public IDictionary Variables { get; } = new Dictionary();
16 |
17 | public string ToJsonText() =>
18 | JsonConvert.SerializeObject(this);
19 | }
20 |
21 | static class Queries
22 | {
23 | internal const string IssueQuery =
24 | @"query ($repo_name: String!) {
25 | repository(owner: ""dotnet"", name: $repo_name) {
26 | issues(last: 50)
27 | {
28 | nodes {
29 | title
30 | number
31 | createdAt
32 | }
33 | }
34 | }
35 | }
36 | ";
37 | internal const string PullRequestQuery =
38 | @"query($repo_name:String!) {
39 | repository(owner: ""dotnet"", name: $repo_name) {
40 | pullRequests(last: 50) {
41 | nodes {
42 | title
43 | number
44 | createdAt
45 | }
46 | }
47 | }
48 | }";
49 | }
50 |
51 | class Program
52 | {
53 | static async Task Main(string[] args)
54 | {
55 | //Follow these steps to create a GitHub Access Token https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token
56 | //Select the following permissions for your GitHub Access Token:
57 | // - repo:status
58 | // - public_repo
59 | var key = GetEnvVariable("GitHubKey",
60 | "You must store you GitHub key in the 'GitHubKey' environment variable",
61 | "");
62 |
63 | var client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue("IssueQueryDemo"))
64 | {
65 | Credentials = new Octokit.Credentials(key)
66 | };
67 |
68 | // Next: Run the issue query.
69 | var issueAndPRQuery = new GraphQLRequest
70 | {
71 | Query = Queries.IssueQuery
72 | };
73 | issueAndPRQuery.Variables["repo_name"] = "docs";
74 |
75 | var postBody = issueAndPRQuery.ToJsonText();
76 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
77 | postBody, "application/json", "application/json");
78 |
79 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
80 | Console.WriteLine(results);
81 |
82 | // Find PRs:
83 | issueAndPRQuery.Query = Queries.PullRequestQuery;
84 | issueAndPRQuery.Variables["repo_name"] = "docs";
85 |
86 | postBody = issueAndPRQuery.ToJsonText();
87 | response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
88 | postBody, "application/json", "application/json");
89 |
90 | results = JObject.Parse(response.HttpResponse.Body.ToString());
91 | Console.WriteLine(results);
92 |
93 | // TODO as a lab:
94 | // 1. Find the latest 50 Open issues in the dotnet/docs repo.
95 | // 2. Find the latest 50 in dotnet-api-docs
96 | // 3. Do the same for PRs in: dotnet/docs, dotnet/dotnet-api-docs, dotnet/samples
97 |
98 | Console.ReadLine();
99 | }
100 |
101 | static string GetEnvVariable(string item, string error, string defaultValue)
102 | {
103 | var value = Environment.GetEnvironmentVariable(item);
104 | if (string.IsNullOrWhiteSpace(value))
105 | {
106 | if (!string.IsNullOrWhiteSpace(error))
107 | {
108 | Console.WriteLine(error);
109 | Environment.Exit(0);
110 | }
111 |
112 | if (!string.IsNullOrWhiteSpace(defaultValue))
113 | {
114 | return defaultValue;
115 | }
116 | }
117 | return value;
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/3 - Exceptions and Errors/Lab/Started/ExceptionsDemo/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ExceptionsDemo
4 | {
5 | class Program
6 | {
7 | static void Main(string[] args)
8 | {
9 | var theBank = new Till(50, 20, 10, 5);
10 |
11 | var expectedTotal = 50 * 1 + 20 * 5 + 10 * 10 + 5 * 20;
12 |
13 | theBank.LogTillStatus();
14 | Console.WriteLine(theBank);
15 | Console.WriteLine($"Expected till value: {expectedTotal}");
16 |
17 | int transactions = 15;
18 | var valueGenerator = new Random((int)DateTime.Now.Ticks);
19 |
20 | while (transactions-- > 0)
21 | {
22 | int itemCost = valueGenerator.Next(2, 50);
23 |
24 | int numOnes = itemCost % 2;
25 | int numFives = (itemCost % 10 > 7) ? 1 : 0;
26 | int numTens = (itemCost % 20 > 13) ? 1 : 0;
27 | int numTwenties = (itemCost < 20) ? 1 : 2;
28 |
29 | try
30 | {
31 | Console.WriteLine($"Customer making a £{itemCost} purchase");
32 | Console.WriteLine($"\t Using {numTwenties} twenties");
33 | Console.WriteLine($"\t Using {numTens} tenners");
34 | Console.WriteLine($"\t Using {numFives} fivers");
35 | Console.WriteLine($"\t Using {numOnes} one-pound coins");
36 |
37 | theBank.MakeChange(itemCost, numTwenties, numTens, numFives, numOnes);
38 |
39 | expectedTotal += itemCost;
40 | }
41 | catch (InvalidOperationException e)
42 | {
43 | Console.WriteLine($"Could not make transaction: {e.Message}");
44 | }
45 |
46 | Console.WriteLine(theBank);
47 | Console.WriteLine($"Expected till value: {expectedTotal}");
48 | Console.WriteLine();
49 | }
50 | }
51 | }
52 |
53 |
54 |
55 | public class Till
56 | {
57 | private int OneDollarBills;
58 | private int FiveDollarBills;
59 | private int TenDollarBills;
60 | private int TwentyDollarBills;
61 |
62 | public Till(int ones, int fives, int tens = 0, int twenties = 0) =>
63 | (OneDollarBills, FiveDollarBills, TenDollarBills, TwentyDollarBills) =
64 | (ones, fives, tens, twenties);
65 |
66 | public void MakeChange(int cost, int twenties, int tens = 0, int fives = 0, int ones = 0)
67 | {
68 | TwentyDollarBills += twenties;
69 | TenDollarBills += tens;
70 | FiveDollarBills += fives;
71 | OneDollarBills += ones;
72 |
73 | int amountPaid = twenties * 20 + tens * 10 + fives * 5 + ones;
74 | int changeNeeded = amountPaid - cost;
75 |
76 | if (changeNeeded < 0)
77 | throw new InvalidOperationException("Not enough money provided");
78 |
79 | Console.WriteLine("Cashier Returns:");
80 |
81 | while ((changeNeeded > 19) && (TwentyDollarBills > 0))
82 | {
83 | TwentyDollarBills--;
84 | changeNeeded -= 20;
85 | Console.WriteLine("\t A twenty");
86 | }
87 |
88 | while ((changeNeeded > 9) && (TenDollarBills > 0))
89 | {
90 | TenDollarBills--;
91 | changeNeeded -= 10;
92 | Console.WriteLine("\t A tenner");
93 | }
94 |
95 | while ((changeNeeded > 4) && (FiveDollarBills > 0))
96 | {
97 | FiveDollarBills--;
98 | changeNeeded -= 5;
99 | Console.WriteLine("\t A fiver");
100 | }
101 |
102 | while ((changeNeeded > 0) && (OneDollarBills > 0))
103 | {
104 | OneDollarBills--;
105 | changeNeeded--;
106 | Console.WriteLine("\t A one");
107 | }
108 |
109 | if (changeNeeded > 0)
110 | throw new InvalidOperationException("Can't make change. Do you have anything smaller?");
111 | }
112 |
113 | public void LogTillStatus()
114 | {
115 | Console.WriteLine("The till currently has:");
116 | Console.WriteLine($"{TwentyDollarBills * 20} in twenties");
117 | Console.WriteLine($"{TenDollarBills * 10} in tens");
118 | Console.WriteLine($"{FiveDollarBills * 5} in fives");
119 | Console.WriteLine($"{OneDollarBills} in ones");
120 | Console.WriteLine();
121 | }
122 |
123 | public override string ToString() =>
124 | $"The till has {TwentyDollarBills * 20 + TenDollarBills * 10 + FiveDollarBills * 5 + OneDollarBills} dollars";
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/3 - Exceptions and Errors/Lab/Completed/ExceptionsDemo/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ExceptionsDemo
4 | {
5 | class Program
6 | {
7 | static void Main(string[] args)
8 | {
9 | var theBank = new Till(50, 20, 10, 5);
10 |
11 | var expectedTotal = 50 * 1 + 20 * 5 + 10 * 10 + 5 * 20;
12 |
13 | theBank.LogTillStatus();
14 | Console.WriteLine(theBank);
15 | Console.WriteLine($"Expected till value: {expectedTotal}");
16 |
17 | int transactions = 500;
18 | var valueGenerator = new Random((int)DateTime.Now.Ticks);
19 |
20 | while (transactions-- > 0)
21 | {
22 | int itemCost = valueGenerator.Next(2, 50);
23 |
24 | int numOnes = itemCost % 2;
25 | int numFives = (itemCost % 10 > 7) ? 1 : 0;
26 | int numTens = (itemCost % 20 > 13) ? 1 : 0;
27 | int numTwenties = (itemCost < 20) ? 1 : 2;
28 |
29 | try
30 | {
31 | Console.WriteLine($"Customer making a £{itemCost} purchase");
32 | Console.WriteLine($"\t Using {numTwenties} twenties");
33 | Console.WriteLine($"\t Using {numTens} tenners");
34 | Console.WriteLine($"\t Using {numFives} fivers");
35 | Console.WriteLine($"\t Using {numOnes} one-pound coins");
36 |
37 | theBank.MakeChange(itemCost, numTwenties, numTens, numFives, numOnes);
38 |
39 | expectedTotal += itemCost;
40 | }
41 | catch (InvalidOperationException e)
42 | {
43 | Console.WriteLine($"Could not make transaction: {e.Message}");
44 | }
45 |
46 | Console.WriteLine(theBank);
47 | Console.WriteLine($"Expected till value: {expectedTotal}");
48 | Console.WriteLine();
49 | }
50 | }
51 | }
52 |
53 |
54 |
55 | public class Till
56 | {
57 | private int OneDollarBills;
58 | private int FiveDollarBills;
59 | private int TenDollarBills;
60 | private int TwentyDollarBills;
61 |
62 | public Till(int ones, int fives, int tens = 0, int twenties = 0) =>
63 | (OneDollarBills, FiveDollarBills, TenDollarBills, TwentyDollarBills) = (ones, fives, tens, twenties);
64 |
65 | public void MakeChange(int cost, int twenties, int tens = 0, int fives = 0, int ones = 0)
66 | {
67 | var twentyDollarBillsInHand = TwentyDollarBills + twenties;
68 | var tenDollarBillsInHand = TenDollarBills + tens;
69 | var fiveDollarBillsInHand = FiveDollarBills + fives;
70 | var onesInHand = OneDollarBills + ones;
71 |
72 | int amountPaid = twenties * 20 + tens * 10 + fives * 5 + ones;
73 | int changeNeeded = amountPaid - cost;
74 |
75 | if (changeNeeded < 0)
76 | throw new InvalidOperationException("Not enough money provided");
77 |
78 | Console.WriteLine("Cashier Returns:");
79 |
80 | while ((changeNeeded > 19) && (TwentyDollarBills > 0))
81 | {
82 | twentyDollarBillsInHand--;
83 | changeNeeded -= 20;
84 | Console.WriteLine("\t A twenty");
85 | }
86 |
87 | while ((changeNeeded > 9) && (TenDollarBills > 0))
88 | {
89 | tenDollarBillsInHand--;
90 | changeNeeded -= 10;
91 | Console.WriteLine("\t A tenner");
92 | }
93 |
94 | while ((changeNeeded > 4) && (FiveDollarBills > 0))
95 | {
96 | fiveDollarBillsInHand--;
97 | changeNeeded -= 5;
98 | Console.WriteLine("\t A fiver");
99 | }
100 |
101 | while ((changeNeeded > 0) && (OneDollarBills > 0))
102 | {
103 | onesInHand--;
104 | changeNeeded--;
105 | Console.WriteLine("\t A one");
106 | }
107 |
108 | if (changeNeeded > 0)
109 | throw new InvalidOperationException("Can't make change. Do you have anything smaller?");
110 |
111 | TwentyDollarBills = twentyDollarBillsInHand;
112 | TenDollarBills = tenDollarBillsInHand;
113 | FiveDollarBills = fiveDollarBillsInHand;
114 | OneDollarBills = onesInHand;
115 | }
116 |
117 | public void LogTillStatus()
118 | {
119 | Console.WriteLine("The till currently has:");
120 | Console.WriteLine($"{TwentyDollarBills * 20} in twenties");
121 | Console.WriteLine($"{TenDollarBills * 10} in tens");
122 | Console.WriteLine($"{FiveDollarBills * 5} in fives");
123 | Console.WriteLine($"{OneDollarBills} in ones");
124 | Console.WriteLine();
125 | }
126 |
127 | public override string ToString() =>
128 | $"The till has {TwentyDollarBills * 20 + TenDollarBills * 10 + FiveDollarBills * 5 + OneDollarBills} dollars";
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/5 - One Abstraction/README.md:
--------------------------------------------------------------------------------
1 | # Async Breakfast demo
2 |
3 | Open the code from the asyncbreakfast-starter.zip
4 |
5 | Run the program. Discuss what's happening. Point out that each long-running task runs to completion before starting the next long running task. That means a cold breakfast, and inefficient use of resources.
6 |
7 | ## Incorporate basic async / await
8 |
9 | Open Program.cs. In turn, make the following to each long-running task:
10 |
11 | 1. Append `Async` to the method signature.
12 | 1. Change the `.Wait()` calls to `await` expressions in each call to `Task.Delay`. Point out that `Task.Delay` simulates some operation that takes time.
13 | 1. Add the `async` modifier to the method.
14 | 1. Change the return type to `Task` where `T` is the previous return type.
15 | 1. Note the new warning in the `Main` method, and add an `await` expression.
16 |
17 | After making all the changes in the first method, note that the `Main` method now has a warning. Take the suggestion to upgrade the project so that `async Task Main` is a valid entry point signature. The program should look like this:
18 |
19 | ```csharp
20 | class Program
21 | {
22 | static async Task Main(string[] args)
23 | {
24 | Coffee cup = PourCoffee();
25 | Console.WriteLine("coffee is ready");
26 | Egg eggs = await FryEggs(2);
27 | Console.WriteLine("eggs are ready");
28 | Bacon bacon = await FryBacon(3);
29 | Console.WriteLine("bacon is ready");
30 | Toast toast = await ToastBread(2);
31 | ApplyButter(toast);
32 | ApplyJam(toast);
33 | Console.WriteLine("toast is ready");
34 | Juice oj = PourOJ();
35 | Console.WriteLine("oj is ready");
36 |
37 | Console.WriteLine("Breakfast is ready!");
38 | }
39 |
40 | private static Juice PourOJ()
41 | {
42 | Console.WriteLine("Pouring Orange Juice");
43 | return new Juice();
44 | }
45 |
46 | private static void ApplyJam(Toast toast) => Console.WriteLine("Putting jam on the toast");
47 |
48 | private static void ApplyButter(Toast toast) => Console.WriteLine("Putting butter on the toast");
49 |
50 | private static async Task ToastBread(int slices)
51 | {
52 | for (int slice = 0; slice < slices; slice++)
53 | Console.WriteLine("Putting a slice of bread in the toaster");
54 | Console.WriteLine("Start toasting...");
55 | await Task.Delay(3000);
56 | Console.WriteLine("Remove toast from toaster");
57 | return new Toast();
58 | }
59 |
60 | private static async Task FryBacon(int slices)
61 | {
62 | Console.WriteLine($"putting {slices} of bacon in the pan");
63 | Console.WriteLine("cooking first side of bacon...");
64 | await Task.Delay(3000);
65 | for (int slice = 0; slice < slices; slice++)
66 | Console.WriteLine("flipping a slice of bacon");
67 | Console.WriteLine("cooking the second side of bacon...");
68 | await Task.Delay(3000);
69 | Console.WriteLine("Put bacon on plate");
70 | return new Bacon();
71 | }
72 |
73 | private static async Task FryEggs(int howMany)
74 | {
75 | Console.WriteLine("Warming the egg pan...");
76 | await Task.Delay(3000);
77 | Console.WriteLine($"cracking {howMany} eggs");
78 | Console.WriteLine("cooking the eggs ...");
79 | await Task.Delay(3000);
80 | Console.WriteLine("Put eggs on plate");
81 | return new Egg();
82 | }
83 |
84 | private static Coffee PourCoffee()
85 | {
86 | Console.WriteLine("Pouring coffee");
87 | return new Coffee();
88 | }
89 | }
90 | ```
91 |
92 | Run the program again. Notice that nothing really changes. Explain that this is because each task is `await`-ed before the the next task completes. In cloud scenarios, that might be fine. The CPU resource could now start tasks for other web requests while awaiting. In this scenario, there is other work to do, so let's continue refactoring a bit.
93 |
94 | ## Start multiple tasks as soon as possible
95 |
96 | The next set of changes is to start subsequent tasks before the preceding task has finished, when possible. Change each await as in the following example:
97 |
98 | ```csharp
99 | Egg eggs = await FryEggs(2);
100 | ```
101 |
102 | Should change to:
103 |
104 | ```csharp
105 | Task eggTask = FryEggs(2);
106 | Egg eggs = await eggTask;
107 | ```
108 |
109 | Then, move all the `await` statements below the lines that start each task. Run it. Now, it finishes faster, because you're starting tasks while awaiting asynchronous work to finish. POint out that this is more like the way people actually make breakfast.
110 |
111 | ## Compose tasks
112 |
113 | The toast illustrates a common problem developers have composing task-based work: There is an asynchronous part, followed by a synchronous part. You want to add butter and jam as soon as the toast finishes, without waiting for the bacon and eggs to be done.
114 |
115 | This could be done with `Task.ContinueWith()`, but there's a better way: refactor it to take advantage of new C# language features, like local functions. Move the toast making code to a local function in `Main`:
116 |
117 | ```csharp
118 | async Task makeToastWithButterAndJamAsync(int number)
119 | {
120 | var plainToast = await ToastBreadAsync(number);
121 | ApplyButter(plainToast);
122 | ApplyJam(plainToast);
123 | return plainToast;
124 | }
125 | ```
126 |
127 | Then, change `Main` to use the new function:
128 |
129 | ```csharp
130 | var toastTask = makeToastWithButterAndJamAsync(2);
131 | // ...
132 | var toast = await toastTask;
133 | Console.WriteLine("toast is ready");
134 | ```
135 |
136 | That's done. Call it good.
137 |
--------------------------------------------------------------------------------
/6 - Task Composition/Lab/Completed/IssuePRreport/IssuePRreport/Program.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using Octokit;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace GitHubActivityReport
10 | {
11 | class GraphQLRequest
12 | {
13 | [JsonProperty("query")]
14 | public string Query { get; set; }
15 |
16 | [JsonProperty("variables")]
17 | public IDictionary Variables { get; } = new Dictionary();
18 |
19 | public string ToJsonText() =>
20 | JsonConvert.SerializeObject(this);
21 | }
22 |
23 | static class Queries
24 | {
25 | internal const string IssueQuery =
26 | @"query ($repo_name: String!) {
27 | repository(owner: ""dotnet"", name: $repo_name) {
28 | issues(last: 50)
29 | {
30 | nodes {
31 | title
32 | number
33 | createdAt
34 | }
35 | }
36 | }
37 | }
38 | ";
39 | internal const string PullRequestQuery =
40 | @"query($repo_name:String!) {
41 | repository(owner: ""dotnet"", name: $repo_name) {
42 | pullRequests(last: 50) {
43 | nodes {
44 | title
45 | number
46 | createdAt
47 | }
48 | }
49 | }
50 | }";
51 | }
52 |
53 | class Program
54 | {
55 | static async Task Main(string[] args)
56 | {
57 | //Follow these steps to create a GitHub Access Token https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token
58 | //Select the following permissions for your GitHub Access Token:
59 | // - repo:status
60 | // - public_repo
61 | var key = GetEnvVariable("GitHubKey",
62 | "You must store you GitHub key in the 'GitHubKey' environment variable",
63 | "");
64 |
65 | var client = new GitHubClient(new Octokit.ProductHeaderValue("IssueQueryDemo"))
66 | {
67 | Credentials = new Octokit.Credentials(key)
68 | };
69 |
70 | // Next: Run the issue query.
71 | await SimpleRunQuery(client);
72 |
73 | await IssuesThenPRsQuery(client);
74 |
75 | await PrintInOrderFinished(client);
76 |
77 | Console.ReadLine();
78 | }
79 |
80 | static async Task SimpleRunQuery(GitHubClient client)
81 | {
82 | JObject results = await RunQuery(client, Queries.IssueQuery, "docs");
83 | Console.WriteLine(results);
84 |
85 | results = await RunQuery(client, Queries.IssueQuery, "dotnet-api-docs");
86 | Console.WriteLine(results);
87 |
88 | // Find PRs:
89 | results = await RunQuery(client, Queries.PullRequestQuery, "samples");
90 | Console.WriteLine(results);
91 |
92 | results = await RunQuery(client, Queries.PullRequestQuery, "dotnet-api-docs");
93 | Console.WriteLine(results);
94 |
95 | results = await RunQuery(client, Queries.PullRequestQuery, "docs");
96 | Console.WriteLine(results);
97 | }
98 |
99 | static async Task IssuesThenPRsQuery(GitHubClient client)
100 | {
101 | var docsIssueTask = RunQuery(client, Queries.IssueQuery, "docs");
102 |
103 | var apidocsIssueTask = RunQuery(client, Queries.IssueQuery, "dotnet-api-docs");
104 |
105 | // Find PRs:
106 | var samplesPRTask = RunQuery(client, Queries.PullRequestQuery, "samples");
107 | var apiDocsPRTask = RunQuery(client, Queries.PullRequestQuery, "dotnet-api-docs");
108 | var docsPRTask = RunQuery(client, Queries.PullRequestQuery, "docs");
109 |
110 | writeData(await docsIssueTask);
111 | writeData(await apidocsIssueTask);
112 | writeData(await samplesPRTask);
113 | writeData(await apiDocsPRTask);
114 | writeData(await docsPRTask);
115 |
116 | void writeData(JObject data) => Console.WriteLine(data);
117 | }
118 |
119 | static async Task PrintInOrderFinished(GitHubClient client)
120 | {
121 | List> queryTasks = new List>
122 | {
123 | RunQuery(client, Queries.IssueQuery, "docs"),
124 | RunQuery(client, Queries.IssueQuery, "dotnet-api-docs"),
125 | RunQuery(client, Queries.PullRequestQuery, "samples"),
126 | RunQuery(client, Queries.PullRequestQuery, "dotnet-api-docs"),
127 | RunQuery(client, Queries.PullRequestQuery, "docs")
128 | };
129 |
130 | while (queryTasks.Any())
131 | {
132 | var finished = await Task.WhenAny(queryTasks);
133 | writeData(await finished);
134 | queryTasks.Remove(finished);
135 | }
136 |
137 | void writeData(JObject data) => Console.WriteLine(data);
138 | }
139 |
140 | static async Task RunQuery(GitHubClient client, string queryText, string repoName)
141 | {
142 | if (client == null)
143 | throw new ArgumentNullException(paramName: nameof(client), "bad null client");
144 | if (string.IsNullOrWhiteSpace(queryText))
145 | throw new ArgumentNullException();
146 | if (string.IsNullOrWhiteSpace(repoName))
147 | throw new ArgumentNullException();
148 |
149 | return runQueryImpl();
150 |
151 | async Task runQueryImpl()
152 | {
153 | Query = queryText
154 | };
155 | issueAndPRQuery.Variables["repo_name"] = repoName;
156 |
157 | var postBody = issueAndPRQuery.ToJsonText();
158 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
159 | postBody, "application/json", "application/json");
160 |
161 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
162 | return results;
163 | }
164 | }
165 |
166 | static string GetEnvVariable(string item, string error, string defaultValue)
167 | {
168 | var value = Environment.GetEnvironmentVariable(item);
169 | if (string.IsNullOrWhiteSpace(value))
170 | {
171 | if (!string.IsNullOrWhiteSpace(error))
172 | {
173 | Console.WriteLine(error);
174 | Environment.Exit(0);
175 | }
176 |
177 | if (!string.IsNullOrWhiteSpace(defaultValue))
178 | {
179 | return defaultValue;
180 | }
181 | }
182 | return value;
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/7 - Advanced Task Composition/Lab/Started/IssuePRreport/IssuePRreport/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Linq;
8 | using Octokit;
9 |
10 | namespace GitHubActivityReport
11 | {
12 | class GraphQLRequest
13 | {
14 | [JsonProperty("query")]
15 | public string Query { get; set; }
16 |
17 | [JsonProperty("variables")]
18 | public IDictionary Variables { get; } = new Dictionary();
19 |
20 | public string ToJsonText() =>
21 | JsonConvert.SerializeObject(this);
22 | }
23 |
24 | static class GraphQLQueries
25 | {
26 | internal const string IssueQuery =
27 | @"query ($repo_name: String!) {
28 | repository(owner: ""dotnet"", name: $repo_name) {
29 | issues(last: 50)
30 | {
31 | nodes {
32 | title
33 | number
34 | createdAt
35 | }
36 | }
37 | }
38 | }
39 | ";
40 | internal const string PullRequestQuery =
41 | @"query($repo_name:String!) {
42 | repository(owner: ""dotnet"", name: $repo_name) {
43 | pullRequests(last: 50) {
44 | nodes {
45 | title
46 | number
47 | createdAt
48 | }
49 | }
50 | }
51 | }";
52 | internal const string PagedIssueQuery =
53 | @"query ($repo_name: String!, $start_cursor:String) {
54 | repository(owner: ""dotnet"", name: $repo_name) {
55 | issues(last: 25, before: $start_cursor)
56 | {
57 | totalCount
58 | pageInfo {
59 | hasPreviousPage
60 | startCursor
61 | }
62 | nodes {
63 | title
64 | number
65 | createdAt
66 | }
67 | }
68 | }
69 | }
70 | ";
71 | }
72 |
73 | class Program
74 | {
75 | static async Task Main(string[] args)
76 | {
77 | //Follow these steps to create a GitHub Access Token https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token
78 | //Select the following permissions for your GitHub Access Token:
79 | // - repo:status
80 | // - public_repo
81 | var key = GetEnvVariable("GitHubKey",
82 | "You must store you GitHub key in the 'GitHubKey' environment variable",
83 | "");
84 |
85 | var client = new GitHubClient(new ProductHeaderValue("IssueQueryDemo"))
86 | {
87 | Credentials = new Credentials(key)
88 | };
89 |
90 | // Next: Run the issue query.
91 | //await SimpleRunQuery(client);
92 |
93 | // await IssuesThenPRsQuery(client);
94 |
95 | //await PrintInOrderFinished(client);
96 |
97 | try
98 | {
99 | var results = await RunPagedQuery(client, GraphQLQueries.PagedIssueQuery, "docs");
100 | Console.WriteLine(results);
101 | }
102 | catch (OperationCanceledException)
103 | {
104 | Console.WriteLine("Work has been cancelled");
105 | }
106 |
107 | Console.ReadLine();
108 | }
109 |
110 | static async Task SimpleRunQuery(GitHubClient client)
111 | {
112 | JObject results = await RunQuery(client, GraphQLQueries.IssueQuery, "docs");
113 | Console.WriteLine(results);
114 |
115 | results = await RunQuery(client, GraphQLQueries.IssueQuery, "dotnet-api-docs");
116 | Console.WriteLine(results);
117 |
118 | // Find PRs:
119 | results = await RunQuery(client, GraphQLQueries.PullRequestQuery, "samples");
120 | Console.WriteLine(results);
121 |
122 | results = await RunQuery(client, GraphQLQueries.PullRequestQuery, "dotnet-api-docs");
123 | Console.WriteLine(results);
124 |
125 | results = await RunQuery(client, GraphQLQueries.PullRequestQuery, "docs");
126 | Console.WriteLine(results);
127 | }
128 |
129 | static async Task IssuesThenPRsQuery(GitHubClient client)
130 | {
131 | var docsIssueTask = RunQuery(client, GraphQLQueries.IssueQuery, "docs");
132 |
133 | var apidocsIssueTask = RunQuery(client, GraphQLQueries.IssueQuery, "dotnet-api-docs");
134 |
135 | // Find PRs:
136 | var samplesPRTask = RunQuery(client, GraphQLQueries.PullRequestQuery, "samples");
137 | var apiDocsPRTask = RunQuery(client, GraphQLQueries.PullRequestQuery, "dotnet-api-docs");
138 | var docsPRTask = RunQuery(client, GraphQLQueries.PullRequestQuery, "docs");
139 |
140 | writeData(await docsIssueTask);
141 | writeData(await apidocsIssueTask);
142 | writeData(await samplesPRTask);
143 | writeData(await apiDocsPRTask);
144 | writeData(await docsPRTask);
145 |
146 | void writeData(JObject data) => Console.WriteLine(data);
147 | }
148 |
149 | static async Task PrintInOrderFinished(GitHubClient client)
150 | {
151 | List> queryTasks = new List>
152 | {
153 | RunQuery(client, GraphQLQueries.IssueQuery, "docs"),
154 | RunQuery(client, GraphQLQueries.IssueQuery, "dotnet-api-docs"),
155 | RunQuery(client, GraphQLQueries.PullRequestQuery, "samples"),
156 | RunQuery(client, GraphQLQueries.PullRequestQuery, "dotnet-api-docs"),
157 | RunQuery(client, GraphQLQueries.PullRequestQuery, "docs")
158 | };
159 |
160 | while (queryTasks.Any())
161 | {
162 | var finished = await Task.WhenAny(queryTasks);
163 | writeData(await finished);
164 | queryTasks.Remove(finished);
165 | }
166 |
167 | void writeData(JObject data) => Console.WriteLine(data);
168 | }
169 |
170 | static async Task RunQuery(GitHubClient client, string queryText, string repoName)
171 | {
172 | var issueAndPRQuery = new GraphQLRequest
173 | {
174 | Query = queryText
175 | };
176 | issueAndPRQuery.Variables["repo_name"] = repoName;
177 |
178 | var postBody = issueAndPRQuery.ToJsonText();
179 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
180 | postBody, "application/json", "application/json");
181 |
182 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
183 |
184 | return results;
185 | }
186 |
187 | static async Task RunPagedQuery(GitHubClient client, string queryText, string repoName)
188 | {
189 | var issueAndPRQuery = new GraphQLRequest
190 | {
191 | Query = queryText
192 | };
193 | issueAndPRQuery.Variables["repo_name"] = repoName;
194 |
195 | JArray finalResults = new JArray();
196 | bool hasMorePages = true;
197 | int pagesReturned = 0;
198 |
199 | // Stop with 10 pages, because these are big repos:
200 | while (hasMorePages && (pagesReturned++ < 10))
201 | {
202 | var postBody = issueAndPRQuery.ToJsonText();
203 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
204 | postBody, "application/json", "application/json");
205 |
206 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
207 |
208 | hasMorePages = (bool)results["data"]["repository"]["issues"]["pageInfo"]["hasPreviousPage"];
209 | issueAndPRQuery.Variables["start_cursor"] = results["data"]["repository"]["issues"]["pageInfo"]["startCursor"].ToString();
210 |
211 | finalResults.Merge(results["data"]["repository"]["issues"]["nodes"]);
212 | }
213 | return finalResults;
214 | }
215 |
216 | private static string GetEnvVariable(string item, string error, string defaultValue)
217 | {
218 | var value = Environment.GetEnvironmentVariable(item);
219 |
220 | if (string.IsNullOrWhiteSpace(value))
221 | {
222 | if (!string.IsNullOrWhiteSpace(error))
223 | {
224 | Console.WriteLine(error);
225 | Environment.Exit(0);
226 | }
227 |
228 | if (!string.IsNullOrWhiteSpace(defaultValue))
229 | {
230 | return defaultValue;
231 | }
232 | }
233 |
234 | return value;
235 | }
236 |
237 | class ProgressStatus : IProgress
238 | {
239 | readonly Action action;
240 |
241 | public ProgressStatus(Action progressAction) =>
242 | action = progressAction;
243 |
244 | public void Report(int value) => action?.Invoke(value);
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/7 - Advanced Task Composition/Lab/Completed/IssuePRreport/IssuePRreport/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Linq;
8 | using Octokit;
9 |
10 | namespace GitHubActivityReport
11 | {
12 | class GraphQLRequest
13 | {
14 | [JsonProperty("query")]
15 | public string Query { get; set; }
16 |
17 | [JsonProperty("variables")]
18 | public IDictionary Variables { get; } = new Dictionary();
19 |
20 | public string ToJsonText() =>
21 | JsonConvert.SerializeObject(this);
22 | }
23 |
24 | static class GraphQLQueries
25 | {
26 | internal const string IssueQuery =
27 | @"query ($repo_name: String!) {
28 | repository(owner: ""dotnet"", name: $repo_name) {
29 | issues(last: 50)
30 | {
31 | nodes {
32 | title
33 | number
34 | createdAt
35 | }
36 | }
37 | }
38 | }
39 | ";
40 | internal const string PullRequestQuery =
41 | @"query($repo_name:String!) {
42 | repository(owner: ""dotnet"", name: $repo_name) {
43 | pullRequests(last: 50) {
44 | nodes {
45 | title
46 | number
47 | createdAt
48 | }
49 | }
50 | }
51 | }";
52 | internal const string PagedIssueQuery =
53 | @"query ($repo_name: String!, $start_cursor:String) {
54 | repository(owner: ""dotnet"", name: $repo_name) {
55 | issues(last: 25, before: $start_cursor)
56 | {
57 | totalCount
58 | pageInfo {
59 | hasPreviousPage
60 | startCursor
61 | }
62 | nodes {
63 | title
64 | number
65 | createdAt
66 | }
67 | }
68 | }
69 | }
70 | ";
71 | }
72 |
73 | class Program
74 | {
75 | static async Task Main(string[] args)
76 | {
77 | //Follow these steps to create a GitHub Access Token https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/#creating-a-token
78 | //Select the following permissions for your GitHub Access Token:
79 | // - repo:status
80 | // - public_repo
81 | var key = GetEnvVariable("GitHubKey",
82 | "You must store you GitHub key in the 'GitHubKey' environment variable",
83 | "");
84 |
85 | var client = new GitHubClient(new Octokit.ProductHeaderValue("IssueQueryDemo"))
86 | {
87 | Credentials = new Octokit.Credentials(key)
88 | };
89 |
90 | // Next: Run the issue query.
91 | //await SimpleRunQuery(client);
92 |
93 | // await IssuesThenPRsQuery(client);
94 |
95 | //await PrintInOrderFinished(client);
96 |
97 | CancellationTokenSource cancellationSource = new CancellationTokenSource();
98 | var progressReporter = new ProgressStatus((num) =>
99 | {
100 | Console.WriteLine($"Received {num} issues in total");
101 | if (num > 100)
102 | cancellationSource.Cancel();
103 | });
104 |
105 | try
106 | {
107 | var results = await RunPagedQuery(client, GraphQLQueries.PagedIssueQuery, "docs",
108 | cancellationSource.Token, progressReporter);
109 | Console.WriteLine(results);
110 | }
111 | catch (OperationCanceledException)
112 | {
113 | Console.WriteLine("Work has been cancelled");
114 | }
115 |
116 | Console.ReadLine();
117 | }
118 |
119 | static async Task SimpleRunQuery(GitHubClient client)
120 | {
121 | JObject results = await RunQuery(client, GraphQLQueries.IssueQuery, "docs");
122 | Console.WriteLine(results);
123 |
124 | results = await RunQuery(client, GraphQLQueries.IssueQuery, "dotnet-api-docs");
125 | Console.WriteLine(results);
126 |
127 | // Find PRs:
128 | results = await RunQuery(client, GraphQLQueries.PullRequestQuery, "samples");
129 | Console.WriteLine(results);
130 |
131 | results = await RunQuery(client, GraphQLQueries.PullRequestQuery, "dotnet-api-docs");
132 | Console.WriteLine(results);
133 |
134 | results = await RunQuery(client, GraphQLQueries.PullRequestQuery, "docs");
135 | Console.WriteLine(results);
136 | }
137 |
138 | static async Task IssuesThenPRsQuery(GitHubClient client)
139 | {
140 | var docsIssueTask = RunQuery(client, GraphQLQueries.IssueQuery, "docs");
141 |
142 | var apidocsIssueTask = RunQuery(client, GraphQLQueries.IssueQuery, "dotnet-api-docs");
143 |
144 | // Find PRs:
145 | var samplesPRTask = RunQuery(client, GraphQLQueries.PullRequestQuery, "samples");
146 | var apiDocsPRTask = RunQuery(client, GraphQLQueries.PullRequestQuery, "dotnet-api-docs");
147 | var docsPRTask = RunQuery(client, GraphQLQueries.PullRequestQuery, "docs");
148 |
149 | writeData(await docsIssueTask);
150 | writeData(await apidocsIssueTask);
151 | writeData(await samplesPRTask);
152 | writeData(await apiDocsPRTask);
153 | writeData(await docsPRTask);
154 |
155 | void writeData(JObject data) => Console.WriteLine(data);
156 | }
157 |
158 | static async Task PrintInOrderFinished(GitHubClient client)
159 | {
160 | List> queryTasks = new List>
161 | {
162 | RunQuery(client, GraphQLQueries.IssueQuery, "docs"),
163 | RunQuery(client, GraphQLQueries.IssueQuery, "dotnet-api-docs"),
164 | RunQuery(client, GraphQLQueries.PullRequestQuery, "samples"),
165 | RunQuery(client, GraphQLQueries.PullRequestQuery, "dotnet-api-docs"),
166 | RunQuery(client, GraphQLQueries.PullRequestQuery, "docs")
167 | };
168 |
169 | while (queryTasks.Any())
170 | {
171 | var finished = await Task.WhenAny(queryTasks);
172 | writeData(await finished);
173 | queryTasks.Remove(finished);
174 | }
175 |
176 | void writeData(JObject data) => Console.WriteLine(data);
177 | }
178 |
179 | static async Task RunQuery(GitHubClient client, string queryText, string repoName)
180 | {
181 | var issueAndPRQuery = new GraphQLRequest
182 | {
183 | Query = queryText
184 | };
185 | issueAndPRQuery.Variables["repo_name"] = repoName;
186 |
187 | var postBody = issueAndPRQuery.ToJsonText();
188 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
189 | postBody, "application/json", "application/json");
190 |
191 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
192 |
193 | return results;
194 | }
195 |
196 | static async Task RunPagedQuery(GitHubClient client, string queryText, string repoName)
197 | {
198 | var issueAndPRQuery = new GraphQLRequest
199 | {
200 | Query = queryText
201 | };
202 | issueAndPRQuery.Variables["repo_name"] = repoName;
203 |
204 | JArray finalResults = new JArray();
205 | bool hasMorePages = true;
206 | int pagesReturned = 0;
207 |
208 | // Stop with 10 pages, because these are big repos:
209 | while (hasMorePages && (pagesReturned++ < 10))
210 | {
211 | var postBody = issueAndPRQuery.ToJsonText();
212 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
213 | postBody, "application/json", "application/json");
214 |
215 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
216 |
217 | hasMorePages = (bool)results["data"]["repository"]["issues"]["pageInfo"]["hasPreviousPage"];
218 | issueAndPRQuery.Variables["start_cursor"] = results["data"]["repository"]["issues"]["pageInfo"]["startCursor"].ToString();
219 |
220 | finalResults.Merge(results["data"]["repository"]["issues"]["nodes"]);
221 | }
222 |
223 | return finalResults;
224 | }
225 |
226 | static async Task RunPagedQuery(GitHubClient client, string queryText, string repoName, CancellationToken cancel, IProgress progress)
227 | {
228 | var issueAndPRQuery = new GraphQLRequest
229 | {
230 | Query = queryText
231 | };
232 | issueAndPRQuery.Variables["repo_name"] = repoName;
233 |
234 | JArray finalResults = new JArray();
235 | bool hasMorePages = true;
236 | int pagesReturned = 0;
237 | int issuesReturned = 0;
238 |
239 | // Stop with 10 pages, because these are big repos:
240 | while (hasMorePages && (pagesReturned++ < 10))
241 | {
242 | var postBody = issueAndPRQuery.ToJsonText();
243 | var response = await client.Connection.Post(new Uri("https://api.github.com/graphql"),
244 | postBody, "application/json", "application/json");
245 |
246 | JObject results = JObject.Parse(response.HttpResponse.Body.ToString());
247 |
248 | int totalCount = (int)results["data"]["repository"]["issues"]["totalCount"];
249 | hasMorePages = (bool)results["data"]["repository"]["issues"]["pageInfo"]["hasPreviousPage"];
250 | issueAndPRQuery.Variables["start_cursor"] = results["data"]["repository"]["issues"]["pageInfo"]["startCursor"].ToString();
251 |
252 | finalResults.Merge(results["data"]["repository"]["issues"]["nodes"]);
253 |
254 | issuesReturned += results["data"]["repository"]["issues"]["nodes"].Count();
255 |
256 | progress?.Report(issuesReturned);
257 |
258 | cancel.ThrowIfCancellationRequested();
259 | }
260 |
261 | return finalResults;
262 | }
263 |
264 | static string GetEnvVariable(string item, string error, string defaultValue)
265 | {
266 | var value = Environment.GetEnvironmentVariable(item);
267 |
268 | if (string.IsNullOrWhiteSpace(value))
269 | {
270 | if (!string.IsNullOrWhiteSpace(error))
271 | {
272 | Console.WriteLine(error);
273 | Environment.Exit(0);
274 | }
275 |
276 | if (!string.IsNullOrWhiteSpace(defaultValue))
277 | {
278 | return defaultValue;
279 | }
280 | }
281 |
282 | return value;
283 | }
284 |
285 | class ProgressStatus : IProgress
286 | {
287 | readonly Action action;
288 |
289 | public ProgressStatus(Action progressAction) =>
290 | action = progressAction;
291 |
292 | public void Report(int value) => action?.Invoke(value);
293 | }
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/xamarinstudio,visualstudio,visualstudiocode,xcode,android,macos,csharp,f#,fastlane,java,jetbrains,linux,monodevelop,objective-c,swift,sublimetext,unity
3 |
4 | ### fastlane ###
5 | # fastlane - A streamlined workflow tool for Cocoa deployment
6 |
7 | # fastlane specific
8 | fastlane/report.xml
9 |
10 | # deliver temporary files
11 | fastlane/Preview.html
12 |
13 | # snapshot generated screenshots
14 | fastlane/screenshots/**/*.png
15 | fastlane/screenshots/screenshots.html
16 |
17 | # scan temporary files
18 | fastlane/test_output
19 |
20 |
21 | ### XamarinStudio ###
22 | bin/
23 | obj/
24 | *.userprefs
25 |
26 |
27 | ### VisualStudioCode ###
28 | .vscode/*
29 | !.vscode/settings.json
30 | !.vscode/tasks.json
31 | !.vscode/launch.json
32 | !.vscode/extensions.json
33 |
34 |
35 | ### Xcode ###
36 | # Xcode
37 | #
38 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
39 |
40 | ## Build generated
41 | build/
42 | DerivedData/
43 |
44 | ## Various settings
45 | *.pbxuser
46 | !default.pbxuser
47 | *.mode1v3
48 | !default.mode1v3
49 | *.mode2v3
50 | !default.mode2v3
51 | *.perspectivev3
52 | !default.perspectivev3
53 | xcuserdata/
54 |
55 | ## Other
56 | *.moved-aside
57 | *.xccheckout
58 | *.xcscmblueprint
59 |
60 |
61 | ### Android ###
62 | # Built application files
63 | *.apk
64 | *.ap_
65 |
66 | # Files for the ART/Dalvik VM
67 | *.dex
68 |
69 | # Java class files
70 | *.class
71 |
72 | # Generated files
73 | gen/
74 | out/
75 | Resource.designer.cs
76 |
77 | # Gradle files
78 | .gradle/
79 |
80 | # Local configuration file (sdk path, etc)
81 | local.properties
82 |
83 | # Proguard folder generated by Eclipse
84 | proguard/
85 |
86 | # Log Files
87 | *.log
88 |
89 | # Android Studio Navigation editor temp files
90 | .navigation/
91 |
92 | # Android Studio captures folder
93 | captures/
94 |
95 | # Intellij
96 | *.iml
97 | .idea/workspace.xml
98 | .idea/tasks.xml
99 | .idea/libraries
100 |
101 | # Keystore files
102 | *.jks
103 |
104 | # External native build folder generated in Android Studio 2.2 and later
105 | .externalNativeBuild
106 |
107 | ### Android Patch ###
108 | gen-external-apklibs
109 |
110 |
111 | ### macOS ###
112 | *.DS_Store
113 | .AppleDouble
114 | .LSOverride
115 |
116 | # Icon must end with two \r
117 | Icon
118 | # Thumbnails
119 | ._*
120 | # Files that might appear in the root of a volume
121 | .DocumentRevisions-V100
122 | .fseventsd
123 | .Spotlight-V100
124 | .TemporaryItems
125 | .Trashes
126 | .VolumeIcon.icns
127 | .com.apple.timemachine.donotpresent
128 | # Directories potentially created on remote AFP share
129 | .AppleDB
130 | .AppleDesktop
131 | Network Trash Folder
132 | Temporary Items
133 | .apdisk
134 |
135 |
136 | ### Csharp ###
137 | ## Ignore Visual Studio temporary files, build results, and
138 | ## files generated by popular Visual Studio add-ons.
139 | ##
140 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
141 |
142 | # User-specific files
143 | *.suo
144 | *.user
145 | *.userosscache
146 | *.sln.docstates
147 | *.vcxproj.filters
148 |
149 | # User-specific files (MonoDevelop/Xamarin Studio)
150 |
151 | # Build results
152 | [Dd]ebug/
153 | [Dd]ebugPublic/
154 | [Rr]elease/
155 | [Rr]eleases/
156 | x64/
157 | x86/
158 | bld/
159 | [Bb]in/
160 | [Oo]bj/
161 | [Ll]og/
162 |
163 | # Visual Studio 2015 cache/options directory
164 | .vs/
165 | # Uncomment if you have tasks that create the project's static files in wwwroot
166 | #wwwroot/
167 |
168 | # MSTest test Results
169 | [Tt]est[Rr]esult*/
170 | [Bb]uild[Ll]og.*
171 |
172 | # NUNIT
173 | *.VisualState.xml
174 | TestResult.xml
175 |
176 | # Build Results of an ATL Project
177 | [Dd]ebugPS/
178 | [Rr]eleasePS/
179 | dlldata.c
180 |
181 | # .NET Core
182 | project.lock.json
183 | project.fragment.lock.json
184 | artifacts/
185 | **/Properties/launchSettings.json
186 |
187 | *_i.c
188 | *_p.c
189 | *_i.h
190 | *.ilk
191 | *.meta
192 | *.obj
193 | *.pch
194 | *.pdb
195 | *.pgc
196 | *.pgd
197 | *.rsp
198 | *.sbr
199 | *.tlb
200 | *.tli
201 | *.tlh
202 | *.tmp
203 | *.tmp_proj
204 | *.vspscc
205 | *.vssscc
206 | .builds
207 | *.pidb
208 | *.svclog
209 | *.scc
210 |
211 | # Chutzpah Test files
212 | _Chutzpah*
213 |
214 | # Visual C++ cache files
215 | ipch/
216 | *.aps
217 | *.ncb
218 | *.opendb
219 | *.opensdf
220 | *.sdf
221 | *.cachefile
222 | *.VC.db
223 | *.VC.VC.opendb
224 |
225 | # Visual Studio profiler
226 | *.psess
227 | *.vsp
228 | *.vspx
229 | *.sap
230 |
231 | # TFS 2012 Local Workspace
232 | $tf/
233 |
234 | # Guidance Automation Toolkit
235 | *.gpState
236 |
237 | # ReSharper is a .NET coding add-in
238 | _ReSharper*/
239 | *.[Rr]e[Ss]harper
240 | *.DotSettings.user
241 |
242 | # JustCode is a .NET coding add-in
243 | .JustCode
244 |
245 | # TeamCity is a build add-in
246 | _TeamCity*
247 |
248 | # DotCover is a Code Coverage Tool
249 | *.dotCover
250 |
251 | # Visual Studio code coverage results
252 | *.coverage
253 | *.coveragexml
254 |
255 | # NCrunch
256 | _NCrunch_*
257 | .*crunch*.local.xml
258 | nCrunchTemp_*
259 |
260 | # MightyMoose
261 | *.mm.*
262 | AutoTest.Net/
263 |
264 | # Web workbench (sass)
265 | .sass-cache/
266 |
267 | # Installshield output folder
268 | [Ee]xpress/
269 |
270 | # DocProject is a documentation generator add-in
271 | DocProject/buildhelp/
272 | DocProject/Help/*.HxT
273 | DocProject/Help/*.HxC
274 | DocProject/Help/*.hhc
275 | DocProject/Help/*.hhk
276 | DocProject/Help/*.hhp
277 | DocProject/Help/Html2
278 | DocProject/Help/html
279 |
280 | # Click-Once directory
281 | publish/
282 |
283 | # Publish Web Output
284 | *.[Pp]ublish.xml
285 | *.azurePubxml
286 | # TODO: Comment the next line if you want to checkin your web deploy settings
287 | # but database connection strings (with potential passwords) will be unencrypted
288 | *.pubxml
289 | *.publishproj
290 |
291 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
292 | # checkin your Azure Web App publish settings, but sensitive information contained
293 | # in these scripts will be unencrypted
294 | PublishScripts/
295 |
296 | # NuGet Packages
297 | *.nupkg
298 | # The packages folder can be ignored because of Package Restore
299 | **/packages/*
300 | # except build/, which is used as an MSBuild target.
301 | !**/packages/build/
302 | # Uncomment if necessary however generally it will be regenerated when needed
303 | #!**/packages/repositories.config
304 | # NuGet v3's project.json files produces more ignoreable files
305 | *.nuget.props
306 | *.nuget.targets
307 |
308 | # Microsoft Azure Build Output
309 | csx/
310 | *.build.csdef
311 |
312 | # Microsoft Azure Emulator
313 | ecf/
314 | rcf/
315 |
316 | # Windows Store app package directories and files
317 | AppPackages/
318 | BundleArtifacts/
319 | _pkginfo.txt
320 |
321 | # Visual Studio cache files
322 | # files ending in .cache can be ignored
323 | *.[Cc]ache
324 | # but keep track of directories ending in .cache
325 | !*.[Cc]ache/
326 |
327 | # Others
328 | ClientBin/
329 | ~$*
330 | *~
331 | *.dbmdl
332 | *.dbproj.schemaview
333 | *.jfm
334 | *.pfx
335 | *.publishsettings
336 | node_modules/
337 | orleans.codegen.cs
338 |
339 | # Since there are multiple workflows, uncomment next line to ignore bower_components
340 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
341 | #bower_components/
342 |
343 | # RIA/Silverlight projects
344 | Generated_Code/
345 |
346 | # Backup & report files from converting an old project file
347 | # to a newer Visual Studio version. Backup files are not needed,
348 | # because we have git ;-)
349 | _UpgradeReport_Files/
350 | Backup*/
351 | UpgradeLog*.XML
352 | UpgradeLog*.htm
353 |
354 | # SQL Server files
355 | *.mdf
356 | *.ldf
357 |
358 | # Business Intelligence projects
359 | *.rdl.data
360 | *.bim.layout
361 | *.bim_*.settings
362 |
363 | # Microsoft Fakes
364 | FakesAssemblies/
365 |
366 | # GhostDoc plugin setting file
367 | *.GhostDoc.xml
368 |
369 | # Node.js Tools for Visual Studio
370 | .ntvs_analysis.dat
371 |
372 | # Visual Studio 6 build log
373 | *.plg
374 |
375 | # Visual Studio 6 workspace options file
376 | *.opt
377 |
378 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
379 | *.vbw
380 |
381 | # Visual Studio LightSwitch build output
382 | **/*.HTMLClient/GeneratedArtifacts
383 | **/*.DesktopClient/GeneratedArtifacts
384 | **/*.DesktopClient/ModelManifest.xml
385 | **/*.Server/GeneratedArtifacts
386 | **/*.Server/ModelManifest.xml
387 | _Pvt_Extensions
388 |
389 | # Paket dependency manager
390 | .paket/paket.exe
391 | paket-files/
392 |
393 | # FAKE - F# Make
394 | .fake/
395 |
396 | # JetBrains Rider
397 | .idea/
398 | *.sln.iml
399 |
400 | # CodeRush
401 | .cr/
402 |
403 | # Python Tools for Visual Studio (PTVS)
404 | __pycache__/
405 | *.pyc
406 |
407 | # Cake - Uncomment if you are using it
408 | # tools/
409 |
410 |
411 | ### F# ###
412 | lib/debug
413 | lib/release
414 | Debug
415 | obj
416 | bin
417 | *.exe
418 | !.paket/paket.bootstrapper.exe
419 |
420 |
421 | ### SublimeText ###
422 | # cache files for sublime text
423 | *.tmlanguage.cache
424 | *.tmPreferences.cache
425 | *.stTheme.cache
426 |
427 | # workspace files are user-specific
428 | *.sublime-workspace
429 |
430 | # project files should be checked into the repository, unless a significant
431 | # proportion of contributors will probably not be using SublimeText
432 | # *.sublime-project
433 |
434 | # sftp configuration file
435 | sftp-config.json
436 |
437 | # Package control specific files
438 | Package Control.last-run
439 | Package Control.ca-list
440 | Package Control.ca-bundle
441 | Package Control.system-ca-bundle
442 | Package Control.cache/
443 | Package Control.ca-certs/
444 | bh_unicode_properties.cache
445 |
446 | # Sublime-github package stores a github token in this file
447 | # https://packagecontrol.io/packages/sublime-github
448 | GitHub.sublime-settings
449 |
450 |
451 | ### Swift ###
452 | # Xcode
453 | #
454 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
455 |
456 | ## Build generated
457 |
458 | ## Various settings
459 |
460 | ## Other
461 | *.xcuserstate
462 |
463 | ## Obj-C/Swift specific
464 | *.hmap
465 | *.ipa
466 | *.dSYM.zip
467 | *.dSYM
468 |
469 | ## Playgrounds
470 | timeline.xctimeline
471 | playground.xcworkspace
472 |
473 | # Swift Package Manager
474 | #
475 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
476 | # Packages/
477 | .build/
478 |
479 | # CocoaPods
480 | #
481 | # We recommend against adding the Pods directory to your .gitignore. However
482 | # you should judge for yourself, the pros and cons are mentioned at:
483 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
484 | #
485 | # Pods/
486 |
487 | # Carthage
488 | #
489 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
490 | # Carthage/Checkouts
491 |
492 | Carthage/Build
493 |
494 | # fastlane
495 | #
496 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
497 | # screenshots whenever they are needed.
498 | # For more information about the recommended setup visit:
499 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
500 |
501 | fastlane/screenshots
502 |
503 |
504 | ### JetBrains ###
505 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
506 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
507 |
508 | # User-specific stuff:
509 |
510 | # Sensitive or high-churn files:
511 | .idea/dataSources/
512 | .idea/dataSources.ids
513 | .idea/dataSources.xml
514 | .idea/dataSources.local.xml
515 | .idea/sqlDataSources.xml
516 | .idea/dynamic.xml
517 | .idea/uiDesigner.xml
518 |
519 | # Gradle:
520 | .idea/gradle.xml
521 |
522 | # Mongo Explorer plugin:
523 | .idea/mongoSettings.xml
524 |
525 | ## File-based project format:
526 | *.iws
527 |
528 | ## Plugin-specific files:
529 |
530 | # IntelliJ
531 | /out/
532 |
533 | # mpeltonen/sbt-idea plugin
534 | .idea_modules/
535 |
536 | # JIRA plugin
537 | atlassian-ide-plugin.xml
538 |
539 | # Crashlytics plugin (for Android Studio and IntelliJ)
540 | com_crashlytics_export_strings.xml
541 | crashlytics.properties
542 | crashlytics-build.properties
543 | fabric.properties
544 |
545 | ### JetBrains Patch ###
546 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
547 |
548 | # *.iml
549 | # modules.xml
550 | # .idea/misc.xml
551 | # *.ipr
552 |
553 |
554 | ### Linux ###
555 |
556 | # temporary files which can be created if a process still has a handle open of a deleted file
557 | .fuse_hidden*
558 |
559 | # KDE directory preferences
560 | .directory
561 |
562 | # Linux trash folder which might appear on any partition or disk
563 | .Trash-*
564 |
565 | # .nfs files are created when an open file is removed but is still being accessed
566 | .nfs*
567 |
568 |
569 | ### MonoDevelop ###
570 | #User Specific
571 | *.usertasks
572 |
573 | #Mono Project Files
574 | *.resources
575 | test-results/
576 |
577 |
578 | ### Objective-C ###
579 | # Xcode
580 | #
581 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
582 |
583 | ## Build generated
584 |
585 | ## Various settings
586 |
587 | ## Other
588 |
589 | ## Obj-C/Swift specific
590 |
591 | # CocoaPods
592 | #
593 | # We recommend against adding the Pods directory to your .gitignore. However
594 | # you should judge for yourself, the pros and cons are mentioned at:
595 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
596 | #
597 | # Pods/
598 |
599 | # Carthage
600 | #
601 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
602 | # Carthage/Checkouts
603 |
604 |
605 | # fastlane
606 | #
607 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
608 | # screenshots whenever they are needed.
609 | # For more information about the recommended setup visit:
610 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
611 |
612 |
613 | # Code Injection
614 | #
615 | # After new code Injection tools there's a generated folder /iOSInjectionProject
616 | # https://github.com/johnno1962/injectionforxcode
617 |
618 | iOSInjectionProject/
619 |
620 | ### Objective-C Patch ###
621 |
622 |
623 | ### Unity ###
624 | /[Ll]ibrary/
625 | /[Tt]emp/
626 | /[Oo]bj/
627 | /[Bb]uild/
628 | /[Bb]uilds/
629 | /Assets/AssetStoreTools*
630 |
631 | # Autogenerated VS/MD/Consulo solution and project files
632 | ExportedObj/
633 | .consulo/*.csproj
634 | .consulo/*.unityproj
635 | .consulo/*.sln
636 | .consulo/*.booproj
637 | .consulo/*.svd
638 |
639 |
640 | # Unity3D generated meta files
641 | *.pidb.meta
642 |
643 | # Unity3D Generated File On Crash Reports
644 | sysinfo.txt
645 |
646 | # Builds
647 | *.unitypackage
648 |
649 |
650 | ### Java ###
651 |
652 | # BlueJ files
653 | *.ctxt
654 |
655 | # Mobile Tools for Java (J2ME)
656 | .mtj.tmp/
657 |
658 | # Package Files #
659 | *.jar
660 | *.war
661 | *.ear
662 |
663 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
664 | hs_err_pid*
665 |
666 |
667 | ### VisualStudio ###
668 | ## Ignore Visual Studio temporary files, build results, and
669 | ## files generated by popular Visual Studio add-ons.
670 | ##
671 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
672 |
673 | # User-specific files
674 |
675 | # User-specific files (MonoDevelop/Xamarin Studio)
676 |
677 | # Build results
678 |
679 | # Visual Studio 2015 cache/options directory
680 | # Uncomment if you have tasks that create the project's static files in wwwroot
681 | #wwwroot/
682 |
683 | # MSTest test Results
684 |
685 | # NUNIT
686 |
687 | # Build Results of an ATL Project
688 |
689 | # .NET Core
690 |
691 |
692 | # Chutzpah Test files
693 |
694 | # Visual C++ cache files
695 |
696 | # Visual Studio profiler
697 |
698 | # TFS 2012 Local Workspace
699 |
700 | # Guidance Automation Toolkit
701 |
702 | # ReSharper is a .NET coding add-in
703 |
704 | # JustCode is a .NET coding add-in
705 |
706 | # TeamCity is a build add-in
707 |
708 | # DotCover is a Code Coverage Tool
709 |
710 | # Visual Studio code coverage results
711 |
712 | # NCrunch
713 |
714 | # MightyMoose
715 |
716 | # Web workbench (sass)
717 |
718 | # Installshield output folder
719 |
720 | # DocProject is a documentation generator add-in
721 |
722 | # Click-Once directory
723 |
724 | # Publish Web Output
725 | # TODO: Comment the next line if you want to checkin your web deploy settings
726 | # but database connection strings (with potential passwords) will be unencrypted
727 |
728 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
729 | # checkin your Azure Web App publish settings, but sensitive information contained
730 | # in these scripts will be unencrypted
731 |
732 | # NuGet Packages
733 | # The packages folder can be ignored because of Package Restore
734 | # except build/, which is used as an MSBuild target.
735 | # Uncomment if necessary however generally it will be regenerated when needed
736 | #!**/packages/repositories.config
737 | # NuGet v3's project.json files produces more ignoreable files
738 |
739 | # Microsoft Azure Build Output
740 |
741 | # Microsoft Azure Emulator
742 |
743 | # Windows Store app package directories and files
744 |
745 | # Visual Studio cache files
746 | # files ending in .cache can be ignored
747 | # but keep track of directories ending in .cache
748 |
749 | # Others
750 |
751 | # Since there are multiple workflows, uncomment next line to ignore bower_components
752 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
753 | #bower_components/
754 |
755 | # RIA/Silverlight projects
756 |
757 | # Backup & report files from converting an old project file
758 | # to a newer Visual Studio version. Backup files are not needed,
759 | # because we have git ;-)
760 |
761 | # SQL Server files
762 |
763 | # Business Intelligence projects
764 |
765 | # Microsoft Fakes
766 |
767 | # GhostDoc plugin setting file
768 |
769 | # Node.js Tools for Visual Studio
770 |
771 | # Visual Studio 6 build log
772 |
773 | # Visual Studio 6 workspace options file
774 |
775 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
776 |
777 | # Visual Studio LightSwitch build output
778 |
779 | # Paket dependency manager
780 |
781 | # FAKE - F# Make
782 |
783 | # JetBrains Rider
784 |
785 | # CodeRush
786 |
787 | # Python Tools for Visual Studio (PTVS)
788 |
789 | # Cake - Uncomment if you are using it
790 | # tools/
791 |
792 | ### VisualStudio Patch ###
793 |
794 | # End of https://www.gitignore.io/api/xamarinstudio,visualstudio,visualstudiocode,xcode,android,macos,csharp,f#,fastlane,java,jetbrains,linux,monodevelop,objective-c,swift,sublimetext,unity
795 |
--------------------------------------------------------------------------------