diff --git a/src/BlogPostCode/SimdCheatCodesForFreePerformance/Discrepancy/Program.cs b/src/BlogPostCode/SimdCheatCodesForFreePerformance/Discrepancy/Program.cs
index b098fce..da55211 100644
--- a/src/BlogPostCode/SimdCheatCodesForFreePerformance/Discrepancy/Program.cs
+++ b/src/BlogPostCode/SimdCheatCodesForFreePerformance/Discrepancy/Program.cs
@@ -97,7 +97,7 @@ public void Setup()
if (xor != 0)
{
- var offset = BitOperations.TrailingZeroCount(xor) / Size;
+ var offset = BitOperations.TrailingZeroCount(xor) / 8;
return i + offset;
}
}
@@ -128,7 +128,7 @@ public void Setup()
if (xor != 0)
{
- var offset = BitOperations.TrailingZeroCount(xor) / Size;
+ var offset = BitOperations.TrailingZeroCount(xor) / 8;
return i + offset;
}
}
@@ -152,8 +152,8 @@ public void Setup()
// Unsafe fun!
// Soundness follows from the fact that we increment by Size up until
// reaching stream1.Length, and we've asserted both streams are of equal size.
- fixed (byte* sensor1Ptr = &MemoryMarshal.GetReference(sensor1))
- fixed (byte* sensor2Ptr = &MemoryMarshal.GetReference(sensor2))
+ fixed (byte* sensor1Ptr = &MemoryMarshal.GetReference(stream1))
+ fixed (byte* sensor2Ptr = &MemoryMarshal.GetReference(stream2))
{
byte* sensor1Current = sensor1Ptr;
byte* sensor2Current = sensor2Ptr;
@@ -193,8 +193,8 @@ public void Setup()
DetachFullBlocks(sensor1, Size, out var stream1, out var remainder1);
DetachFullBlocks(sensor2, Size, out var stream2, out var remainder2);
- ref byte sensor1Current = ref MemoryMarshal.GetReference(sensor1);
- ref byte sensor2Current = ref MemoryMarshal.GetReference(sensor2);
+ ref byte sensor1Current = ref MemoryMarshal.GetReference(stream1);
+ ref byte sensor2Current = ref MemoryMarshal.GetReference(stream2);
for (var i = 0; i < stream1.Length; i += Size)
{
@@ -233,8 +233,8 @@ public void Setup()
// Unsafe fun!
// Soundness follows from the fact that we increment by Size up until
// reaching stream1.Length, and we've asserted both streams are of equal size.
- fixed (byte* sensor1Ptr = &MemoryMarshal.GetReference(sensor1))
- fixed (byte* sensor2Ptr = &MemoryMarshal.GetReference(sensor2))
+ fixed (byte* sensor1Ptr = &MemoryMarshal.GetReference(stream1))
+ fixed (byte* sensor2Ptr = &MemoryMarshal.GetReference(stream2))
{
byte* sensor1Current = sensor1Ptr;
byte* sensor2Current = sensor2Ptr;
@@ -274,8 +274,8 @@ public void Setup()
DetachFullBlocks(sensor1, Size, out var stream1, out var remainder1);
DetachFullBlocks(sensor2, Size, out var stream2, out var remainder2);
- ref byte sensor1Current = ref MemoryMarshal.GetReference(sensor1);
- ref byte sensor2Current = ref MemoryMarshal.GetReference(sensor2);
+ ref byte sensor1Current = ref MemoryMarshal.GetReference(stream1);
+ ref byte sensor2Current = ref MemoryMarshal.GetReference(stream2);
for (var i = 0; i < stream1.Length; i += Size)
{
diff --git a/src/Sorcery.Blogging/Tag.cs b/src/Sorcery.Blogging/Tag.cs
index e058112..2a0f85b 100644
--- a/src/Sorcery.Blogging/Tag.cs
+++ b/src/Sorcery.Blogging/Tag.cs
@@ -4,7 +4,7 @@ namespace Sorcery.Blogging;
public readonly record struct Tag
{
- public static readonly Tag None = new Tag();
+ public static readonly Tag None;
public string Value { get; private init; }
diff --git a/src/Sorcery.ModularCourse/GlobalSuppressions.cs b/src/Sorcery.ModularCourse/GlobalSuppressions.cs
new file mode 100644
index 0000000..c880d6a
--- /dev/null
+++ b/src/Sorcery.ModularCourse/GlobalSuppressions.cs
@@ -0,0 +1,14 @@
+// Licensed under MIT, copyright Mateusz Gienieczko, all rights reserved.
+
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage(
+ "Naming", "CA1716:Identifiers should not match keywords",
+ Justification = "We will not be consuming Sorcery from VB...",
+ Scope = "type",
+ Target = "~T:Sorcery.ModularCourse.Module")]
diff --git a/src/Sorcery/Pages/Sourcery/Posts/AlphabetDoesNotGoAToZ.razor b/src/Sorcery/Pages/Sourcery/Posts/AlphabetDoesNotGoAToZ.razor
index 5cfd75e..05378b7 100644
--- a/src/Sorcery/Pages/Sourcery/Posts/AlphabetDoesNotGoAToZ.razor
+++ b/src/Sorcery/Pages/Sourcery/Posts/AlphabetDoesNotGoAToZ.razor
@@ -177,7 +177,7 @@ static bool IsAllLettersRegex(string text) => AllLettersRegex.IsMatch(text);
think to arrive at a fix? Well, the correct problem diagnosis is "those folks whose service we use are bad at programming,
and they screwed the ExtractAlpha operation", which is not the best impression to leave on your customers.
And it's not obvious on how to work around this either! In my case the users also have access to a custom RegexReplace
- operation, but remember that they're not necessarily experts. Not all programmers aren't skilled in regexes, so requiring an end
+ operation, but remember that they're not necessarily experts. Not all programmers are skilled in regexes, so requiring an end
user to diagnose and fix this issue by themselves would be crazy.
@@ -205,7 +205,7 @@ static bool IsAllLettersRegex(string text) => AllLettersRegex.IsMatch(text);
One could argue with my labelling of the [a-zA-Z] version as "incorrect" and call it an exaggeration.
After all, it depends – correctness only makes sense with respect to requirements. And there are requirements
that warrant [a-zA-Z] – maybe you're parsing valid C identifiers, or URLs, or some other well-defined
- format that is designed around thhe ASCII charset. That's true, if your specific requirements are to recognise latin
+ format that is designed around the ASCII charset. That's true, if your specific requirements are to recognise latin
letters then it's totally fine. But for many of us that's not the case. Regexes are commonly used to validate
user input, and in most cases restricting that to ASCII is artificial. Support for Unicode should be the
default, and restricting the character set an exception made for a good reason. Computers are no longer exclusively
diff --git a/src/Sorcery/Pages/Sourcery/Posts/SimdCheatCodesForFreePerformance.razor b/src/Sorcery/Pages/Sourcery/Posts/SimdCheatCodesForFreePerformance.razor
index 2597124..12819c0 100644
--- a/src/Sorcery/Pages/Sourcery/Posts/SimdCheatCodesForFreePerformance.razor
+++ b/src/Sorcery/Pages/Sourcery/Posts/SimdCheatCodesForFreePerformance.razor
@@ -32,7 +32,7 @@
Enter the world of SIMD. This series will introduce the concept from first principles,
starting with simple concepts at a high-level and then gradually getting us closer to the metal.
- We'll start with good old C# code, without any magic tricks. All the code for this part can be found
+ Part 1 starts with good old C# code, without any magic tricks. All the code for this part can be found
on my GitHub.
@@ -49,7 +49,7 @@
is not actually needed to apply this concept. Consider the following toy problem.
- The Dicrepancy Problem. —
+ The Discrepancy Problem. —
We are given two streams of $8$-bit measurements from two sensors over some time period.
We expect them to be the same, but since anomalies can occur we want to detect the first place
at which they differ, if it exists.
@@ -192,7 +192,8 @@ private int? Simd64(ReadOnlySpan sensor1, ReadOnlySpan sensor2)
Let's compare the performance of these solutions! I've also implemented Simd32,
which is a trivial change of the Size constant and BitConverter
calls. I run the benchmark on two streams of $1$ megabyte, with only the final byte
- differing. For completeness, this is run with .NET 7.0.4 on an AMD Ryzen 9 7950X.
+ differing between them, so that all bytes have to be compared.
+ For completeness, this is run with .NET 7.0.4 on an AMD Ryzen 9 7950X.
@@ -226,12 +227,10 @@ private int? Simd64(ReadOnlySpan sensor1, ReadOnlySpan sensor2)
$4 \times$ speedup basically for free!
-It might be slightly surprising that we get $2$/$4$ times speedups while increasing
+It might be slightly surprising that we get $3$/$5$ times speedups while increasing
the size of operations $4$/$8$ times. Such is reality, though, the packed version generates
-more complex code and introduces its own overhead. Note, however, that the fact that
-$32$-bit SIMD is almost exactly twice as fast as sequential is just a coincidence, but that $64$-bit is almost
-exactly twice as fast as $32$-bit is not – the code between the two is identical
-save for the size difference, so there's no additional overhead, just pure gains.
+more complex code and introduces its own overhead. There are many variables at play in high-performance
+code, including memory latency, and without proper profiling we cannot identify them all.
@@ -253,7 +252,7 @@ save for the size difference, so there's no additional overhead, just pure gains
Try to come up with a solution that finds which element of an $8$-element vector is different
without using any loops. Here are the building blocks that I give you –
- you can perform any bitwise operation on the vectors as you want, and you can quickly access some information about
+ you can perform any bitwise operation on the vectors you want, and you can quickly access some information about
the vector, like the number of non-zero elements or the location of first such element.
@@ -272,11 +271,11 @@ save for the size difference, so there's no additional overhead, just pure gains
return i + offset;
}
")" />
-
+
-
+
@@ -297,6 +296,279 @@ save for the size difference, so there's no additional overhead, just pure gains
Wide Vector SIMD
+
+ On a $64$-bit platform the $8$-byte solution utilises the general-purpose registers fully.
+ The idea behind what we usually call SIMD is rather simple – let's make bigger registers
+ so that we can handle more data with a single instruction. Such extensions are sometimes called
+ vectorial extensions and give us access to the SIMD world where each value/register
+ is a vector of atomic values.
+
+
+ This is the moment where implementation
+ gets hairy – those additional registers are provided by platform extensions which are,
+ well, platform-specific. There is a whole bunch of them providing registers of different sizes
+ ($128$, $256$, $512$ bits), different instruction sets, and different performance characteristics.
+ Most modern x86 CPUs have standardised access to most interesting operations, but not all.
+ Moreover, ARM SIMD is completely different from x86 SIMD, and there's a separate extension set
+ for SIMD on WebAssembly.
+ Instead of diving deep into the various SSEs, AVXs, and NEONs of the world,
+ we will use a layer of abstraction that I will call portable SIMD.
+
+
+ Different languages have different ways of providing portable SIMD. In C# land these facilities
+ live in System.Runtime.Intrinsics as
+ Vector128,
+ Vector256,
+ and (in .NET 8) Vector512.
+ The main purpose of portable SIMD is to allow the programmer to write code with vector acceleration in mind,
+ but leave it to the compiler to figure out what specific extensions the target platform supports
+ and what code to generate to get the best performance. This is especially natural for a JIT-compiled language
+ like C#, where that's already the jitter's job for other code.
+
+
+ Before we jump into SIMD world, here are some preliminaries.
+ We'll call operations on vectors that produce other vectors maps,
+ and operations that reduce vectors into primitive types projections.
+ There are a few ground rules we have to follow:
+
+
+ all operations on SIMD vectors are immutable – they read the inputs and produce a fresh result;
+
+
+ we cannot read the contents of a SIMD vector directly, we first need to project
+ it to a primitive type, like an integer;
+
+
+ projection is expensive – we get vectorial speedups while keeping within the SIMD world,
+ and pay a cost to exit into general-purpose value world.
+
+
+
+ SIMD Maps and Projections
+
+
+ One of the fundamental binary operation classes
+ is comparison – we're specifically interested in element-wise equality.
+ The semantics are: the $i$-th element of the result is all-ones if the $i$-th elements
+ of input are equal, and zero otherwise.
+
+
+ There is a number of standard projections of vectors into fundamental types.
+ For example, one can take the sum of all elements in a vector and obtain a regular integer.
+ In our case, we are interested in the very special movemask operation.
+ It produces a primitive integer whose each bit corresponds to the state of an element in the input vector –
+ The $i$-th bit of the result is set if the most significant bit of the $i$-th element is set.
+
+
+
+
+
+
+
+
+
+
+
+
+ We can combine the equality check with a movemask to obtain a mask
+ similar to the one we worked on in our $32$/$64$-bit algorithms before.
+ Equality produces vectors of all-one and all-zero values, which are
+ then collapsed by movemask into singular bits of the result.
+
+
+
+ This is a small $32$-bit example, just for illustrative purposes.
+ $4$-bit integers don't exist, they can't hurt you.
+
+
+
+
+
+
+
+ SIMD in C#
+
+
+ How do we implement this in C#?
+ First we choose the vector size – let's start with $128$-bit, code for larger ones will be similar –
+ and the C# vector type – we'll be using Vector128<T>.
+ The type parameter of the struct is the type of the values we are interpreting as vectors. In our discrepancy
+ problem it's byte – Vector128<byte> represents $16$ individual byte values.
+ As an example, you can imagine a similar problem on streams of $32$-bit int values, and then we'd
+ be using Vector128<int> as vectors of $4$ individual ints.
+
+
+ The tricky part is getting the vectors loaded. Normally when coding in C# we really don't want to think about
+ low-level interactions with memory, but here we're pretty close to the hardware. SIMD can only load
+ data to a vector if it exists in a single contiguous slice of memory – the $16$ bytes we want to load
+ have to be next to each other, and we need to give the pointer to the first one. We don't want to deal with
+ pointers, pointers are icky and require magical unsafe stuff. Fortunately, we can use a
+ reference variable to, ekhem, refer to the start of our stream,
+ and then a nuint to store the offset within the stream for loading purposes.
+
+ Reference Variables
+
+
+ Quick detour if you're not familiar with ref in C#.
+ Reference variables, also called ref locals, are local variables one level
+ of indirection higher than regular variables – instead of a value, they hold
+ a reference to a different storage place with the value. This is a bit like a pointer,
+ only that a C# ref has a bunch of guarantees around it for memory safety
+ – it's impossible to have a ref that is invalid, the compiler will stop
+ you before that.
+
+
+ For example, this:
+
+ prints 1 1 2 2See SharpLab gist.,
+ while any attempts to return a potentially invalid ref will fail:
+
+
+
+ The Code
+
+
+ Okay, let's actually write and run that.
+ First we will take our ReadOnlySpans and ask a helper class
+ MemoryMarshal,
+ which exists specifically to help translating between a ReadOnlySpan and its contiguous slice memory representation.
+ to give us a ref to the first byte, which can be loaded into a Vector128<byte>.
+ We will use the equality map and movemask projection to get a bitmask where set bits signify elements
+ that are the same. By negating that mask and looking at trailing zeroes we can replicate the same
+ algorithm we used before for our $32$ and $64$-bit approaches. We then need to use another helper
+
+ Unsafe.Add;
+ note that we could also keep track of the offset manually in a nuint and pass it to the LoadUnsafe function,
+ but I didn't want to introduce the concept of a native int here.
+
+ to move our refs to the streams forward and loop.
+
+ sensor1, ReadOnlySpan sensor2)
+{
+ if (sensor1.Length != sensor2.Length)
+ {
+ throw new ArgumentException(""Unequal stream lengths"");
+ }
+ const int Size = 16;
+
+ // Take the cleanly divisible part and leave the reminder for later.
+ DetachFullBlocks(sensor1, Size, out var stream1, out var remainder1);
+ DetachFullBlocks(sensor2, Size, out var stream2, out var remainder2);
+ ref byte sensor1Current = ref MemoryMarshal.GetReference(stream1);
+ ref byte sensor2Current = ref MemoryMarshal.GetReference(stream2);
+
+ for (var i = 0; i < stream1.Length; i += Size)
+ {
+ // SAFETY: This operation is safe if the ref bytes actually contain
+ // enough initialised values to fill the vector.
+ // DetachFullBlocks is the safeguard here.
+ Vector128 vector1 = Vector128.LoadUnsafe(ref sensor1Current);
+ Vector128 vector2 = Vector128.LoadUnsafe(ref sensor2Current);
+
+ Vector128 cmpeq = Vector128.Equals(vector1, vector2);
+ uint mask = Vector128.ExtractMostSignificantBits(cmpeq);
+
+ // Discrepancy occurs only if two elements were different,
+ // and one of the bits of the mask is NOT set.
+ if (mask != 0xFFFF)
+ {
+ var offset = BitOperations.TrailingZeroCount(~mask);
+ return i + offset;
+ }
+
+ // SAFETY: This is the same invariant - there have to be at least
+ // Size elements to skip over.
+ // DetachFullBlocks guarantees this for the entire loop.
+ sensor1Current = ref Unsafe.Add(ref sensor1Current, Size);
+ sensor2Current = ref Unsafe.Add(ref sensor2Current, Size);
+ }
+
+ return Sequential(remainder1, remainder2) + stream1.Length;
+}
+")" />
+
+ Whew. The reference handling is a bit scary with all the "unsafe", but the rest is rather
+ simple. So, it's a bit harder to code in this paradigm, but are the performance gains
+ worth it?
+
+
+ Judge by yourself. I included a $256$-bit version, which is analogous to the above code
+ – Vector128 is replaced with Vector256, Size
+ is set to $32$, ant the mask is compared against 0xFFFFFFFF.
+
+
+
+
+
Method
+
Mean
+
Error
+
Ratio
+
+
+
+
+
Sequential
+
267.44 μs
+
0.833 μs
+
1.00
+
+
+
Simd32
+
93.49 μs
+
0.191 μs
+
0.35
+
+
+
Simd64
+
49.86 μs
+
0.962 μs
+
0.19
+
+
+
Simd128Portable
+
26.87 μs
+
0.019 μs
+
0.10
+
+
+
Simd256Portable
+
15.42 μs
+
0.055 μs
+
0.06
+
+
+
+
+ Changing nothing about the fundamental algorithm we made it $20\times$ faster
+ just by switching to SIMD! And apart from a few relatively uncomplicated ref-offset operations that's
+ just plain C# code, the JIT does the heavy lifting for us.
+
+
+ The question remains, though – what exactly is it lifting and why is it so heavy?
+ Well, that's why this is merely part one.
+
+
+
+
+ We will discuss juicy details in the next part. To not miss it, subscribe to the RSS feed of Sourcery!
+
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/00-DotnetTaxonomy.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/00-DotnetTaxonomy.razor
index 404f46f..bcaf96d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/00-DotnetTaxonomy.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/00-DotnetTaxonomy.razor
@@ -36,10 +36,10 @@
It is oriented towards safety and developer productivity and characterised by:
- memory safety;
- type safety and static type analysis;
- flexibility – you get a box of ergonomic tools that allow you to solve your problem efficiently...
- ... but designed so that you won't shoot yourself in the foot in the process.
+ memory safety;
+ type safety and static type analysis;
+ flexibility – you get a box of ergonomic tools that allow you to solve your problem efficiently...
+ ... but designed so that you won't shoot yourself in the foot in the process.
That is, until we decide we want to get rid of that with dynamic or unsafe, but that will be tackled
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/01-ConfiguringYourEnvironment.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/01-ConfiguringYourEnvironment.razor
index 680acb1..752d3e2 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/01-ConfiguringYourEnvironment.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/01-ConfiguringYourEnvironment.razor
@@ -77,15 +77,15 @@ sudo apt-get install -y dotnet-sdk-6.0")/>
Here are the extensions that we need to install in Code:
-
+
C# (Marketplace)
– IDE for C# .NET development
-
+
.NET Interactive Notebooks (Marketplace)
– to use interactive notebooks in Code.
-
+
NuGet Gallery (Marketplace)
– for browsing and installing C# libraries.
@@ -94,17 +94,17 @@ sudo apt-get install -y dotnet-sdk-6.0")/>
There are also a few very helpful extensions that you might want to install if you haven't already:
-
+
Error Lens (Marketplace)
– shows diagnostics (warnings, errors, etc.) inline in your code. Very useful in notebooks where we show intentionally wrong
code, so that you know at a glance what the error is.
-
+
Test Explorer UI (Marketplace)
and .NET Core Test Explorer (Marketplace)
– view and run .NET tests from the VS Code UI.
-
+
Code Spell Checker (Marketplace)
– so that your final project doesn't contain those emberesing speling miskates.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/02-HelloWorld.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/02-HelloWorld.razor
index cf49984..ddb50df 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/02-HelloWorld.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/02-HelloWorld.razor
@@ -69,7 +69,7 @@ internal class Program
Let's go through that line by line.
-
+
The first line is what we call a using directive. It tells the compiler to bring into scope
all types defined in the System namespace. That's another new word – a namespace is a logical
@@ -78,7 +78,7 @@ internal class Program
System.Text.Regex. If you know Java's packages – this is more or less it. Namespaces are in PascalCase.
-
+
Line number two is the declaration of our namespace in which the types defined in this file should be located.
By convention, the namespace is the same as the directory path. Since Program.cs is located directly
@@ -87,21 +87,21 @@ internal class Program
You should definitely follow that convention, as otherwise the project structure gets very confusing.
-
+
Line three declares the Program class. The internal modifier restricts its visibility – we will cover that later.
Because of line two, the Program class is located in the HelloWorld namespace. Types are named in PascalCase.
Blocks of code such as class bodies are delimited in braces, which in C# we put on separate lines.
-
+
Next we declare a static method on the Program class. It is called Main, takes
an array of string values called args as a parameter, and returns nothing (because it's void).
Methods are also in PascalCase, but their parameters are in camelCase.
-
+
Finally, we call the WriteLine method on the Console class and pass a single string argument to it.
Where is Console from? Well, its fully qualified name is System.Console. Every
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/03-BasicTypes.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/03-BasicTypes.razor
index 1dc2623..c00537d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/03-BasicTypes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/03-BasicTypes.razor
@@ -13,42 +13,42 @@
In this section we'll cover
-
+
Basic built-in types.
-
+
Variable declarations and type inference.
-
+
Constant locals.
-
+
Numeric literals.
-
+
Implicit and explicit numeric conversions.
-
+
Operators.
-
+
Strings and string literals.
-
+
String formatting.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/04-ControlFlow.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/04-ControlFlow.razor
index 78fc7df..631a190 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/04-ControlFlow.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/04-ControlFlow.razor
@@ -13,17 +13,17 @@
In this section we'll cover
-
+
Control flow with if and loops.
-
+
Variable scoping rules.
-
+
Definite assignment rules.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/05-Arrays.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/05-Arrays.razor
index 59cbcff..fddc385 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/05-Arrays.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/05-Arrays.razor
@@ -12,22 +12,22 @@
In this section we'll cover
-
+
Declaring and initialising arrays.
-
+
Indexing into arrays with Index and Range.
-
+
Very basics of a foreach loop.
-
+
Multidimensional and jagged arrays.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/Introduction.razor
index 7be1911..c368587 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/00-Basics/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/00-Basics/Introduction.razor
@@ -7,11 +7,11 @@
- what is C#, .NET, and all those others acronyms;
- how to configure your environment;
- how to write a C# program;
- basic built-in types, variables, control-flow, scoping rules;
- arrays, indexing, ranges and the foreach statement;
+ what is C#, .NET, and all those others acronyms;
+ how to configure your environment;
+ how to write a C# program;
+ basic built-in types, variables, control-flow, scoping rules;
+ arrays, indexing, ranges and the foreach statement;
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/00-Classes.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/00-Classes.razor
index 9ed3ebc..5ff630c 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/00-Classes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/00-Classes.razor
@@ -13,52 +13,52 @@
In this section we'll cover
-
+
Access modifiers.
-
+
Classes.
-
+
Fields and readonly.
-
+
Methods.
-
+
Properties.
-
+
Constructors.
-
+
Initialisers.
-
+
Target typed new.
-
+
Overloading methods.
-
+
Static members.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/01-inheritance.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/01-inheritance.razor
index 7d98b7e..f6a1194 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/01-inheritance.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/01-inheritance.razor
@@ -13,32 +13,32 @@
In this section we'll cover
-
+
Inheriting from other classes.
-
+
Invoking the base constructor.
-
+
Virtual methods and overriding.
-
+
Hiding.
-
+
Preventing inheritance with sealed.
-
+
Casting.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/02-AbstractTypes.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/02-AbstractTypes.razor
index 3d5e5e2..4cbbb62 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/02-AbstractTypes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/02-AbstractTypes.razor
@@ -12,17 +12,17 @@
In this section we'll cover
-
+
Abstract classes.
-
+
Interfaces.
-
+
Static classes.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/03-Strings.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/03-Strings.razor
index 66b174c..854a81b 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/03-Strings.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/03-Strings.razor
@@ -12,27 +12,27 @@
In this section we'll cover
-
+
Basic string operations.
-
+
String comparison.
-
+
String indexer.
-
+ StringBuilder.
-
+ nameof
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/04-Attributes.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/04-Attributes.razor
index 0baef74..8c09099 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/04-Attributes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/04-Attributes.razor
@@ -12,7 +12,7 @@
In this section we'll cover
-
+
Using attributes as metadata.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/05-TestingWithXUnit.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/05-TestingWithXUnit.razor
index 1582195..3e594bf 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/05-TestingWithXUnit.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/05-TestingWithXUnit.razor
@@ -10,17 +10,17 @@
We won't be using a notebook for this one, as we need an actual project to run the tests.
In .NET there are three major test frameworks:
-
+
MSUnit
-
+
NUnit
-
+
xUnit
@@ -42,12 +42,12 @@
which is a workflow where you repeatedly alternate between two modes:
-
+
write new unit tests until they stop passing;
-
+
write production code until the existing unit tests all pass.
@@ -76,17 +76,17 @@
We're going to create a simple Modulo(int x, int m) method that works as follows:
-
+
if m or x is zero, returns zero;
-
+
else if x is positive returns $x \mod |m|$;
-
+
otherwise, returns $-(|x| \mod |m|)$;
@@ -249,17 +249,17 @@ public class CalculatorUnitTests
which is a useful template for writing tests that makes their logic clear to follow.
-
+ Arrange: initialize all objects required for the test.
-
+ Act: perform the operations whose effects we want to test and save their results.
-
+ Assert: validate that the result conforms to the expectations.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Assignment-DungeonWalker.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Assignment-DungeonWalker.razor
index 4346421..ca031af 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Assignment-DungeonWalker.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Assignment-DungeonWalker.razor
@@ -259,7 +259,6 @@ The room is empty except for a large chest. And inside...
This is tested by the "Loot" test group, worth 0.25 points.
- Task 2. – Dungeons
There are two Room layouts implemented, Basic and Adventure.
@@ -300,7 +299,6 @@ The room is empty except for a large chest. And inside...
Fill out the DungeonWalker.Logic.Factories.HeroFactory static class to return correct
instances for Heroes:
-
@@ -378,27 +376,27 @@ The room is empty except for a large chest. And inside...
in a professional setting.
-
+
Use the correct naming convention for a given member.
-
+
DO NOT use abbreviations. If you look at the BCL you won't find monstrosities
like strcmp or inet_pton.
Only universally recognisable abbreviations of computer terms are allowed, like
HttpClient, TcpSocket or XmlSerializer.
-
+
Standard code guidelines that apply everywhere else also apply in C#:
avoid code duplication, don't create overly long methods, use methods
from the standard library where applicable.
-
+
Remember that C# is supposed to be simple and elegant.
Use the features we know to reduce the amount of code and number
of lines. Use expression bodied members, if possible.
Use string interpolation instead of manual string addition, if possible.
-
+
Follow best OOP practices. Don't introduce inheritance where it's not needed.
Seal your classes by default.
Prefer abstract types to concrete ones as parameters and return types.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Introduction.razor
index 32b3d6a..525755d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/01-ObjectOrientation/Introduction.razor
@@ -8,12 +8,12 @@
- classes, methods, fields, properties;
- abstract classes, interfaces, inheritance;
- static classes, attributes;
- operating on strings;
- testing with xUnit;
- how to use GitHub Classroom and submit your first microassignment.
+ classes, methods, fields, properties;
+ abstract classes, interfaces, inheritance;
+ static classes, attributes;
+ operating on strings;
+ testing with xUnit;
+ how to use GitHub Classroom and submit your first microassignment.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/00-Memory.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/00-Memory.razor
index d15476c..f10006e 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/00-Memory.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/00-Memory.razor
@@ -17,7 +17,7 @@
and the collector.
-
+ Allocator is responsible for allocating and initialising memory requested
by a program running on the CLR. In theory, its job is simple – when it gets a request
@@ -35,7 +35,7 @@
based on number of allocations performed, average lifetimes of allocated objects, etc.
-
+ Collector performs the actual garbage collection. The .NET GC is a
mark-and-sweep collector. When triggered, it scans the program
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/01-ReferenceTypesAndValueTypes.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/01-ReferenceTypesAndValueTypes.razor
index bc239cc..980edd0 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/01-ReferenceTypesAndValueTypes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/01-ReferenceTypesAndValueTypes.razor
@@ -12,27 +12,27 @@
In this section we'll cover
-
+
The difference between reference types and value types.
-
+
Declaring custom value types – enum and struct.
-
+
Default values.
-
+
Value types and memory management.
-
+
Boxing.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/02-OperatorOverloading.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/02-OperatorOverloading.razor
index ee3f519..a789254 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/02-OperatorOverloading.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/02-OperatorOverloading.razor
@@ -12,17 +12,17 @@
In this section we'll cover
-
+
How to overload operators.
-
+
Declaring regular methods alongside operators for better interoperability.
-
+
Sanity checks for operator overloading.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/03-Exceptions.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/03-Exceptions.razor
index 8a4ce9e..dc57c4c 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/03-Exceptions.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/03-Exceptions.razor
@@ -12,12 +12,12 @@
In this section we'll cover
-
+
The throw instruction.
-
+
Defensive programming.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/04-Nullability.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/04-Nullability.razor
index 53a393b..c81defe 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/04-Nullability.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/04-Nullability.razor
@@ -12,27 +12,27 @@
In this section we'll cover
-
+
The Billion-Dollar Mistake.
-
+
Nullable Reference Types.
-
+
Null-coalescing operators.
-
+
Nullable Value Types – Nullable<T>.
-
+
Lifted operators.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/05-Casting.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/05-Casting.razor
index 86e1bf4..e9f2c5f 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/05-Casting.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/05-Casting.razor
@@ -12,12 +12,12 @@
In this section we'll cover
-
+
Casting with as and is.
-
+
When to use which cast.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/06-PassByReference.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/06-PassByReference.razor
index 93ab03b..9f6e917 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/06-PassByReference.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/06-PassByReference.razor
@@ -12,22 +12,22 @@
In this section we'll cover
-
+
Pass-by-value vs pass-by-reference.
-
+
Pass-by-reference with out and ref.
-
+
The TryX pattern.
-
+
Ignoring out parameters.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Assignment-LustrousLoot.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Assignment-LustrousLoot.razor
index 8decb97..f7df929 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Assignment-LustrousLoot.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Assignment-LustrousLoot.razor
@@ -260,22 +260,22 @@ The room is empty except for a large chest. And inside...
-
+ Common – $\times 1$ multiplier
-
+ Uncommon – $\times 1.25$ multiplier
-
+ Rare – $\times 1.5$ multiplier
-
+ Heroic – $\times 2.0$ multiplier
diff --git a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Introduction.razor
index 4b70c74..aed0c5d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/02-ReferencesAndValues/Introduction.razor
@@ -7,15 +7,15 @@
- how CLR manages memory, basics of Garbage Collection;
- the difference between reference types and value types;
- switch
- default values;
- operator overloading;
- exceptions for defensive programming;
- nullability, Nullable Reference Types;
- casting, is, as;
- passing by reference.
+ how CLR manages memory, basics of Garbage Collection;
+ the difference between reference types and value types;
+ switch
+ default values;
+ operator overloading;
+ exceptions for defensive programming;
+ nullability, Nullable Reference Types;
+ casting, is, as;
+ passing by reference.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/00-Indexers.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/00-Indexers.razor
index 18350ce..d9a3b62 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/00-Indexers.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/00-Indexers.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
Defining custom indexers.
-
+
Supporting Index and Range.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/01-Generics.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/01-Generics.razor
index ee639cb..31f4079 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/01-Generics.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/01-Generics.razor
@@ -12,32 +12,32 @@
In this section we'll cover
-
+
Motivation behind generics.
-
+
Generic types and methods.
-
+
How the compiler treats generics.
-
+
Generic constraints.
-
+
Inheriting from generic types.
-
+
Nullability with generics.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/02-Equality.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/02-Equality.razor
index 139882a..ee11738 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/02-Equality.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/02-Equality.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
Default equality semantics.
-
+
Reference equality vs value equality.
-
+
A case study – defining rational numbers.
-
+ GetHashCode.
-
+ IEquatable<T>.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/03-Ordering.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/03-Ordering.razor
index dba4651..69e1fac 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/03-Ordering.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/03-Ordering.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+ IComparable<T>
-
+
Overloading relational operators.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/04-ExplicitInterfaceImplementations.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/04-ExplicitInterfaceImplementations.razor
index 2794d52..febd820 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/04-ExplicitInterfaceImplementations.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/04-ExplicitInterfaceImplementations.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Problems with implementing multiple interfaces.
-
+
Resolving ambiguity.
-
+
Hiding interface methods.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/05-Collections.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/05-Collections.razor
index 40d747c..f7e9e44 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/05-Collections.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/05-Collections.razor
@@ -12,49 +12,49 @@
In this section we'll cover
-
+
Generic collections in the BCL.
-
+ IEnumerable<T>.
-
+ ICollection<T>, IList<T>, IDictionary<TKey, TValue>.
-
+
Read-only views.
-
+ List<T>.
-
+ HashSet<T>.
-
+ SortedSet<T>.
-
+ Queue<T>.
-
+ Stack<T>.
-
+ SortedList<T>.
-
+ LinkedList<T>.
-
+ PriorityQueue<TElement, TPriority>.
-
+ Dictionary<TKey, TValue>.
-
+ SortedDictionary<TKey, TValue>.
-
+
Immutable collections.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/06-Comparers.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/06-Comparers.razor
index ea613ec..adfb0f7 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/06-Comparers.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/06-Comparers.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
Plugging in equality with IEqualityComparer<T>.
-
+
Plugging in ordering with IComparer<T>.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/07-Tuples.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/07-Tuples.razor
index 5a8735c..327d3dc 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/07-Tuples.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/07-Tuples.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Creating tuples.
-
+
Tuple types and names.
-
+
Tuple equality and comparison.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/08-Deconstruction.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/08-Deconstruction.razor
index e585774..a36d6ad 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/08-Deconstruction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/08-Deconstruction.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
Deconstructing tuples.
-
+
Deconstructing KeyValuePair<TKey, TValue>.
-
+
Code patterns using deconstruction.
-
+
Implementing deconstruction for custom types.
-
+
Discarding irrelevant values.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/09-NestedTypes.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/09-NestedTypes.razor
index cf1cce9..6db4a7f 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/09-NestedTypes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/09-NestedTypes.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
How to declare nested types.
-
+
Defining tightly coupled types as nested types.
-
+
Hiding implementation details in nested types.
-
+
Factory pattern with nested types.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/10-HashCodeAsAMutableStruct.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/10-HashCodeAsAMutableStruct.razor
index da651f1..c9f7e09 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/10-HashCodeAsAMutableStruct.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/10-HashCodeAsAMutableStruct.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
How to create more complex hash codes.
-
+ HashCode being an example of a mutable struct.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/11-Records.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/11-Records.razor
index d30ba73..cfcc94e 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/11-Records.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/11-Records.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
Declaring record classes.
-
+
Primary constructors.
-
+
Synthesised equality comparison.
-
+
Nondestructive mutation with with.
-
+
Record structs.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/12-PatternMatching.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/12-PatternMatching.razor
index 5ea9103..17cfea4 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/12-PatternMatching.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/12-PatternMatching.razor
@@ -12,31 +12,31 @@
In this section we'll cover
-
+ is expressions and switch expressions.
-
+
The var pattern.
-
+
Constant patterns.
-
+
The discard pattern.
-
+
Type patterns.
-
+
Positional patterns.
-
+
Relational patterns.
-
+
Pattern combinators.
-
+
Property patterns.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/Introduction.razor
index 8cf290d..a48089d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/03-GenericsAndCollections/Introduction.razor
@@ -10,19 +10,19 @@
- defining indexers;
- generic types and methods;
- how nullability interacts with generics;
- referential and structural equality;
- custom equality;
- GetHashCode;
- IEquatable<T>;
- IComparable<T>;
- basic collections in the BCL;
- comparer types IEqualityComparer<T> and IComparer<T>;
- tuples;
- deconstruction;
- nested types.
+ defining indexers;
+ generic types and methods;
+ how nullability interacts with generics;
+ referential and structural equality;
+ custom equality;
+ GetHashCode;
+ IEquatable<T>;
+ IComparable<T>;
+ basic collections in the BCL;
+ comparer types IEqualityComparer<T> and IComparer<T>;
+ tuples;
+ deconstruction;
+ nested types.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/00-GenericVariance.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/00-GenericVariance.razor
index 3fb7ab4..035b222 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/00-GenericVariance.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/00-GenericVariance.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
The motivation behind variance.
-
+
Covariance in generic interfaces.
-
+
Contravariance in generic interfaces.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/01-Iterators.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/01-Iterators.razor
index 897993d..39fd870 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/01-Iterators.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/01-Iterators.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Defining iterators.
-
+
Lazy execution.
-
+
Guards in iterator methods.
-
+
High-level sketch of iterator implementation.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/02-ExtensionMethods.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/02-ExtensionMethods.razor
index df21e10..044e920 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/02-ExtensionMethods.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/02-ExtensionMethods.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Defining extension methods.
-
+
Extending interfaces.
-
+
Generic extension methods.
-
+
Scoping rules and namespace guidelines.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/03-AnonymousTypes.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/03-AnonymousTypes.razor
index 9521cc5..322c23d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/03-AnonymousTypes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/03-AnonymousTypes.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Creating anonymous types.
-
+
Anonymous types under the hood.
-
+
Nondestructive mutation via the with expression.
-
+
Anonymous types vs. tuples.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/04-Delegates.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/04-Delegates.razor
index 935bd2a..215933f 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/04-Delegates.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/04-Delegates.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Delegates as strongly-typed function pointers.
-
+
Standard delegate types: Action and Func.
-
+
Delegate variance.
-
+
Converting method groups to delegates.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/05-LambdaExpressions.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/05-LambdaExpressions.razor
index 2668cba..e8da125 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/05-LambdaExpressions.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/05-LambdaExpressions.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
Using lambda expressions.
-
+
Type conversions and lambdas.
-
+
Captures.
-
+
Lambda implementation details.
-
+
Static lambdas.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/06-LinqQueries.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/06-LinqQueries.razor
index 0923ffe..34b8d87 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/06-LinqQueries.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/06-LinqQueries.razor
@@ -12,37 +12,37 @@
In this section we'll cover
-
+
Basic LINQ operators – Select, Where, OrderBy, GroupBy.
-
+
Aggregation – Count, Sum, Average, Max, Min, Aggregate
-
+
Predicates – All, Any, First, Last, Single
-
+
Combining sequences – Concat, SelectMany, Zip
-
+
Set operations – Contains, Distinct, Union, Except, Intersect
-
+
Construction operations – Empty, Range, Repeat
-
+
Collecting – ToList, ToArray, ToHashSet, ToDictionary
-
+
Deferred execution.
-
+
Multiple enumeration.
-
+
Query syntax, Join.
-
+
MoreLINQ.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/07-LocalMethods.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/07-LocalMethods.razor
index d9098ed..e8a48f7 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/07-LocalMethods.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/07-LocalMethods.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
Declaring local methods.
-
+
Using local methods to separate preconditions from iterators.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Assignment-LayeredLayouts.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Assignment-LayeredLayouts.razor
index 25ad4e2..788cd36 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Assignment-LayeredLayouts.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Assignment-LayeredLayouts.razor
@@ -41,10 +41,10 @@
shortest paths. The recursive definition of layers is as follows:
-
+
the layer $0$ is exactly the vertices without any incoming edges;
-
+
the layer $i + 1$ is exactly the vertices that have a direct edge incoming from
some vertex in layer $i$.
@@ -57,13 +57,13 @@
Pick any vertex from the graph and run a depth-first search. We keep the usual status of a vertex as we go:
active if we've recursed down one of its neighbours, visited if we already processed all of its non-DFS-parent neighbours, unvisited otherwise. When we consider an edge there are three cases:
-
+
our neighbour has already been processed – ignore it;
-
+
our neighbour has not been visited before – continue DFS recursively;
-
+
neighbour has been visited but is still active – we have found a cycle, terminate the algorithm.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Introduction.razor
index 7b2c70f..be3c8a9 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/04-LINQ/Introduction.razor
@@ -10,14 +10,14 @@
- generic variance;
- iterators (a.k.a. generators);
- extension methods;
- ad-hoc data with anonymous types;
- first-class strongly typed functions – delegates;
- lambda expressions;
- LINQ queries;
- local methods.
+ generic variance;
+ iterators (a.k.a. generators);
+ extension methods;
+ ad-hoc data with anonymous types;
+ first-class strongly typed functions – delegates;
+ lambda expressions;
+ LINQ queries;
+ local methods.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/00-SimpleThreading.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/00-SimpleThreading.razor
index f03154f..305b265 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/00-SimpleThreading.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/00-SimpleThreading.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
The low-level Thread API basics.
-
+
The lock statement.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/01-Events.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/01-Events.razor
index c517b76..2def3cb 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/01-Events.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/01-Events.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Manually implementing the observer pattern.
-
+
The event members.
-
+
Multicast delegates.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/02-ExceptionHandling.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/02-ExceptionHandling.razor
index 96a14d3..bc79658 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/02-ExceptionHandling.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/02-ExceptionHandling.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
The try, catch blocks.
-
+
Creating custom exceptions.
-
+
Filtering exceptions with when.
-
+
The finally block.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/03-DisposableResources.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/03-DisposableResources.razor
index 8fac2f9..803d00c 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/03-DisposableResources.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/03-DisposableResources.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Why we need explicit cleanup.
-
+
The System.IO.File API.
-
+ IDisposable, using blocks and declarations.
-
+
Implementing IDisposable.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/04-AsyncAndAwait.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/04-AsyncAndAwait.razor
index 4b79935..93ddf8e 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/04-AsyncAndAwait.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/04-AsyncAndAwait.razor
@@ -12,31 +12,31 @@
In this section we'll cover
-
+
What is an asynchronous operation.
-
+
Why asynchrony is useful.
-
+ Task and Task<TResult&rt;.
-
+ async and await.
-
+
Task combinators – Task.WhenAll, Task.WhenAny.
-
+
Async state machines.
-
+
Intuition behind await.
-
+
Precondition checks in async methods.
-
+
Holding resources across await.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/05-ThreadPool.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/05-ThreadPool.razor
index 115cf13..c0ee398 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/05-ThreadPool.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/05-ThreadPool.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
What is the ThreadPool?
-
+
Executing code on the ThreadPool with Task.Run.
-
+
Brief implementation sketch.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/06-Cancellation.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/06-Cancellation.razor
index 07af416..abaafed 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/06-Cancellation.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/06-Cancellation.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
Cooperative cancellation.
-
+ CancellationToken.
-
+ CancellationTokenSource.
-
+
Cancelling async methods.
-
+
Cancelling non-async methods.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/07-AsyncInterfacesAndValueTask.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/07-AsyncInterfacesAndValueTask.razor
index ea3ecca..1ab4a17 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/07-AsyncInterfacesAndValueTask.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/07-AsyncInterfacesAndValueTask.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+ IAsyncDisposable, await using.
-
+ IAsyncEnumerable<T>, async iterators, await foreach.
-
+
Async LINQ with System.Linq.Async.
-
+ ValueTask for mostly synchronous returns.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Assignment-PersistedPathways.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Assignment-PersistedPathways.razor
index 3a1721e..d0d41ee 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Assignment-PersistedPathways.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Assignment-PersistedPathways.razor
@@ -211,24 +211,24 @@ public interface IDungeonRepository
Remember that:
-
+
All I/O operations on the filesystem may fail.
The exceptions called by this must be wrapped in a FileSystemRepositoryException
that inherits from DungeonRepositoryException.
-
+
Precondition checks for async operations should happen synchronously.
There are no tests for this, but I will point that out during code review. Use the local method pattern
to deal with that.
-
+
All system file handles are expensive resources and must be properly disposed of.
-
+
The CancellationToken instances passed to the methods should be used for
all async operations that support cancellation.
-
+
Always choose the async overload for operations on the filesystem.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Introduction.razor
index ec976ed..9f24c98 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/05-Asynchrony/Introduction.razor
@@ -9,19 +9,19 @@
- simple Thread usage;
- the lock statement
- events;
- exception handling, catch, finally blocks;
- disposable resources, IDisposable;
- asynchrony;
- Task, Task<TResult&rt;;
- async, await;
- Task.WhenAll, Task.WhenAny;
- intuition behind await;
- the ThreadPool;
- entering the pool with Task.Run and Task.Factory.StartNew;
- supporting cancellation with CancellationToken and CancellationTokenSource.
+ simple Thread usage;
+ the lock statement
+ events;
+ exception handling, catch, finally blocks;
+ disposable resources, IDisposable;
+ asynchrony;
+ Task, Task<TResult&rt;;
+ async, await;
+ Task.WhenAll, Task.WhenAny;
+ intuition behind await;
+ the ThreadPool;
+ entering the pool with Task.Run and Task.Factory.StartNew;
+ supporting cancellation with CancellationToken and CancellationTokenSource.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/00-ExpressionTrees.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/00-ExpressionTrees.razor
index 5e07e70..a44d9c8 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/00-ExpressionTrees.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/00-ExpressionTrees.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Lambdas into expression trees.
-
+
Expression trees into lambdas.
-
+
The IQueryable<T> interface.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/01-LinqToSql.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/01-LinqToSql.razor
index 9ac7134..dbad2e7 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/01-LinqToSql.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/01-LinqToSql.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Defining a model and a DbContext.
-
+
Migrations.
-
+
Querying DbSet<TEntity> with LINQ.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/02-NavigationProperties.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/02-NavigationProperties.razor
index 6a5ceac..714311a 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/02-NavigationProperties.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/02-NavigationProperties.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Defining one-to-many/many-to-many relationships.
-
+
Querying related data with implict JOINs.
-
+
Explicit JOINs with Include.
-
+
Nullable analysis and navigation properties.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/03-Inheritance.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/03-Inheritance.razor
index 9dbe1f7..62f1c37 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/03-Inheritance.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/03-Inheritance.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
Table-per-Hierarchy model.
-
+
Table-per-Type model.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/04-Tracking.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/04-Tracking.razor
index 4bd88ab..bab4db0 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/04-Tracking.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/04-Tracking.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
Entity changes tracking.
-
+ DbContext lifetime and the Unit of Work pattern.
-
+
Referential equality as source-of-truth.
-
+ AsNoTracking.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/05-Transactions.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/05-Transactions.razor
index bf3d227..7f4652d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/05-Transactions.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/05-Transactions.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Default transaction behavior.
-
+ Database.BeginTransaction() transactions.
-
+
Ambient/transient transactions with System.Transactions.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Assignment-EldritchEntities.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Assignment-EldritchEntities.razor
index 466c119..fc612d9 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Assignment-EldritchEntities.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Assignment-EldritchEntities.razor
@@ -185,18 +185,18 @@ COMMIT;
Hints and warnings:
-
+
Remember that the save semantics are "overwrite if exists". For a database that means that you need to
first remove a graph with the given name, if it exists.
-
+
@{
var navigation = CourseBook.CSharpCourse["entity-framework"]["navigation-properties"];
}
The relation model is quite deep. You will need to use stuff described in @(navigation.DisplayName),
including ThenInclude, which you can find in the linked resource: Eager Loading of Related Data.
-
+
During loading, try to perform as much simplification as possible inside the query. If you need only a list of numbers
of vertices to reconstruct the graph, SELECT only the numbers, not all the edges. If you need
to sort some data, try to sort it on the database instead of locally.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Introduction.razor
index 95d1aca..235f1e6 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/06-EntityFramework/Introduction.razor
@@ -8,20 +8,20 @@
- expression trees, the Expression type hierarchy;
- IQueryable<T> and IQueryProvider;
- database providers;
- Database First;
- Code First;
- defining a DbContext;
- migrations;
- querying with LINQ-to-SQL;
- modelling relations with navigation properties;
- implicit JOIN;
- explicit JOIN with Include;
- modelling inheritance;
- change tracking;
- transactions.
+ expression trees, the Expression type hierarchy;
+ IQueryable<T> and IQueryProvider;
+ database providers;
+ Database First;
+ Code First;
+ defining a DbContext;
+ migrations;
+ querying with LINQ-to-SQL;
+ modelling relations with navigation properties;
+ implicit JOIN;
+ explicit JOIN with Include;
+ modelling inheritance;
+ change tracking;
+ transactions.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/00-MinimalHttpServer.razor b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/00-MinimalHttpServer.razor
index f6bb672..10bc2e0 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/00-MinimalHttpServer.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/00-MinimalHttpServer.razor
@@ -199,21 +199,21 @@ public class WeatherForecastController : ControllerBase
Let's break it down:
-
+
The controller is defined as a class inheriting from ControllerBase
with the special ApiControllerAttribute.
-
+
The RouteAttribute defines how to access the controller.
The special '[controller]' value evaluates to the controller class name
with 'Controller' stripped, so in this case it's 'weatherForecast'
(URL-s are case insensitive).
-
+
The controller has a ILogger<WeatherForecastController> dependency
that is magically given to it in the constructor. This is Dependency Injection.
-
+
The controller's action is called Get and returns a sequence of
WeatherForecast objects. The Name is a friendly name
given to an action, e.g. allowing us to create a link to this particular action
diff --git a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/01-DependencyInjection.razor b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/01-DependencyInjection.razor
index ffa3c31..a6c56f5 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/01-DependencyInjection.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/01-DependencyInjection.razor
@@ -79,33 +79,33 @@ curl https://localhost:20443/adder/sum
for, like, infinitely many reasons.
-
+
If NumberRepository ever needs a different way to be constructed,
for example we decide to add logging and make the constructor take a logger,
we would have to change every place with new NumberRepository.
Imagine we had hundreds of actions, each instantiating its own NumberRepository...
-
+
If AdderDbContext ever needs a different way to be constructed we are also screwed.
You can probably see how this extrapolates – the issue becomes more and more pronounced with
every layer underneath us. At some point instantiating a high-level service in a controller
might require new-ing a dozen objects into existence.
-
+
Why does the AdderController even care about whether NumberRepository
uses a DbContext underneath? What if we decide that we need to hold our numbers in
a cloud storage? Do we go around and change every instantiation of the repository to use the
cloud provider instead of AdderDbContext?
-
+
For that matter, why does the AdderController even care that AdderDbContextexists.
Again, does our controller need to know about every single type in the project to perform its job? If it
needed a high-level service it'd necessarily know all about all the types below.
-
+
Why is the controller managing the lifetime of a DbContext? Just asking questions.
-
+
Finally, and most importantly – there is no way to test this method. None. Any unit test would necessarily have
to construct a database itself and then seed it with data to observe the results.
Changing our code to use a different database immediately invalidates all these tests.
@@ -239,14 +239,14 @@ public class AdderControllerUnitTests
lifetime types:
-
+
Transient – a new instance gets created every time the service is requested.
-
+
Scoped – an intance is created per scope. By and large it means one per
HTTP request, but one can create more granular scopes manually.
-
+
Singleton – one instance gets created and reused throughout the application's
lifetime.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/03-ModelBinding.razor b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/03-ModelBinding.razor
index c58ad39..aed8c58 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/03-ModelBinding.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/03-ModelBinding.razor
@@ -47,18 +47,18 @@ public async Task> GetDungeonAsync(int id)
A few things here:
-
+
The RouteAttribute specifies a parameter named id
that gets mapped from the last part of the URL. So /dungeon/dungeons/3 will map to this action with
parameter id = 3. That parameter is mapped to the method's parameter with the same name.
-
+
The return type is ActionResult<DungeonView>. This is a special ASP.NET Core type
that represents a bunch of possible responses. There's an implicit conversion from T to
ActionResult<T>, as well as conversions from things like NotFoundResult,
BadRequestResult, etc.
-
+
We can return different HTTP responses using helper methods defined on ControllerBase.
In this case we return a 404 Not Found if a Dungeon with given ID does not exist.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/04-Logging.razor b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/04-Logging.razor
index bbb408d..ce796f9 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/04-Logging.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/04-Logging.razor
@@ -49,23 +49,23 @@ _logger.LogCritical(""Could not connect to the application database."");
There are 6 logging levels.
-
+
Trace – most detailed information used for tracking down hard-to-find bugs.
-
+
Debug – detailed development logs; should not be enabled on production.
-
+
Information – general flow of the application.
-
+
Warning – unexpected conditions that should be brought to attention, but
don't cause direct failure.
-
+
Error – a failure condition for the current operation.
-
+
Critical – a failure condition for the entire application that should cause
the server administrator to be woken up in the middle of the night.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/Introduction.razor
index fcfa859..4865919 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/07-ASP.NETCore/Introduction.razor
@@ -19,12 +19,12 @@
- setting up a minimal HTTP server;
-
+ setting up a minimal HTTP server;
+
defining a model that can be mapped from REST requests and into REST responses;
- dependency injection;
- Swagger;
+ dependency injection;
+ Swagger;
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/00-TypesAndInstances.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/00-TypesAndInstances.razor
index 185a75a..b2a81d7 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/00-TypesAndInstances.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/00-TypesAndInstances.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+ typeof and GetType.
-
+
The Type class.
-
+
Working with generic types with reflection.
-
+
Instantiating objects with Activator.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/01-Members.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/01-Members.razor
index 9ed4fbf..6cc2961 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/01-Members.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/01-Members.razor
@@ -12,22 +12,22 @@
In this section we'll cover
-
+ BindingFlags.
-
+ MemberInfo.
-
+
Invoking methods via MethodInfo.
-
+
Invoking methods via a constructed delegate.
-
+
Accessing properties.
-
+
Accessing fields.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/02-Attributes.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/02-Attributes.razor
index 93d2f5f..0c005db 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/02-Attributes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/02-Attributes.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Obtaining attribute metadata.
-
+
Defining custom attributes.
-
+
Using attribute metadata at runtime.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/03-SystemAttributes.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/03-SystemAttributes.razor
index 334371d..0bf90a6 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/03-SystemAttributes.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/03-SystemAttributes.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Attributes interpreted by the compiler at callsites.
-
+
Attributes influencing codegen.
-
+
Nullable static analysis attributes.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/04-Dynamic.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/04-Dynamic.razor
index 5651bc9..fc53c7b 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/04-Dynamic.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/04-Dynamic.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
The dynamic type.
-
+
Dynamic binding and overload resolution.
-
+ ExpandoObject.
-
+
Dynamic Language Runtime.
-
+
IronPython
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Assignment-AdventurersAssemble.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Assignment-AdventurersAssemble.razor
index 2b494b6..fb1d6d1 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Assignment-AdventurersAssemble.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Assignment-AdventurersAssemble.razor
@@ -25,16 +25,16 @@
To be approprietly loaded, a type needs to:
-
+
be tagged with the HeroClassAttribute;
-
+
be a subclass of DungeonWalker.Logic.Characters.Hero;
-
+
have a parameterless constructor;
-
+
have a name specified in the attribute not conflicting with any previously loaded Hero.
@@ -66,13 +66,13 @@ dotnet publish ./dynamic/Outside.Heroes -o ./dynamic/bin
Implement the missing functions:
-
+ GetHeroAsync
-
+ LoadAllHeroesFromAssembly
-
+ LoadHeroClass
@@ -86,16 +86,16 @@ dotnet publish ./dynamic/Outside.Heroes -o ./dynamic/bin
instance has extension methods defined for all of the failure cases we defined:
-
+ FailedToLoadHeroClassDueToNoAttribute
-
+ FailedToLoadHeroClassDueToLackOfAParameterlessConstructor
-
+ FailedToLoadHeroClassDueToNotSubclassingHero
-
+ FailedToLoadHeroClassDueToConflictingName
diff --git a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Introduction.razor
index b4a17fe..d900fe2 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/08-Reflection/Introduction.razor
@@ -20,19 +20,19 @@
- retrieving type metadata;
-
+ retrieving type metadata;
+
retrieving metadata for members;
- invoking methods via reflection;
- modifying fields and properties via reflection;
- retrieving attribute metadata;
- defining custom attributes;
- using attributes at runtime;
- Caller Info Attributes;
- system attributes;
- the Dynamic Language Runtime;
- calling Python from .NET;
+ invoking methods via reflection;
+ modifying fields and properties via reflection;
+ retrieving attribute metadata;
+ defining custom attributes;
+ using attributes at runtime;
+ Caller Info Attributes;
+ system attributes;
+ the Dynamic Language Runtime;
+ calling Python from .NET;
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/00-GarbageCollectionDetails.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/00-GarbageCollectionDetails.razor
index 20b7d66..fdd3eab 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/00-GarbageCollectionDetails.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/00-GarbageCollectionDetails.razor
@@ -104,15 +104,15 @@
Summary
-
+
More allocations – more collections. Decreasing allocations can improve performance by
reducing GC time.
-
+
Allocating a lot of long-lived objects is a surefire way of killing your app's performance
by increasing the frequency of Gen2 collections.
-
+
The Large Object Heap is best when it's not touched. We will talk on how to achieve that
in @arrayPoolingSection.DisplayName.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/01-Benchmarking.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/01-Benchmarking.razor
index 15a54de..d82dd11 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/01-Benchmarking.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/01-Benchmarking.razor
@@ -11,13 +11,13 @@
we reach our goals. These are:
-
+
Measure,
-
+
Measure, and
-
+
Measure.
@@ -131,19 +131,19 @@
-
+
$1.0$ – search through unchanged input, which contains the word "interrelationships";
-
+
$0.75$ – replaces "interrelationship", "interrelationships" and "interrelationship's"
in the input with '#' characters; this makes sure that the longest substring contained within the input is "interrelations"
($14$ characters), so both solutions need to do some work to find it.
-
+
$0.5$ – additionally removes "interrelation", "interrelations",
"relationship", and "relationships". The longest match is "relations" ($9$ characters).
-
+
$0.0$ – removes all substrings of "interrelationships", so that the result is an empty string.
@@ -253,36 +253,36 @@ Intel Core i5-8600K CPU 3.60GHz (Coffee Lake), 1 CPU, 6 logical and 6 physical c
Benchmarking Golden Rules
-
+
Use vetted tools that deal with the benchmark infrastructure for you.
-
+
Never rely on system timestamps for benchmarking, it's hard to find a less reliable timestamping tool.
-
+
Run benchmarks in a controlled environment. Best case scenario you'd have a separate machine,
be it a second PC, a laptop, or a VM in the cloud, that can run the benchmark and only the benchmark.
Most surefire way to ruin a benchmark is try to use Visual Studio while it's executing. On my PC it skews results
by large percentages and even changes modalities of distributions.
-
+
Keep in mind that the compiler will try to pull the rug from under your feet. It will mercilessly inline methods,
discard unused results, unroll loops, and sometimes optimise your whole benchmark away. Remember to always use the
results of a computation, to slap NoInlining if you want to make sure a method is completely isolated,
etc.
-
+
Use real data and workflows if possible. Running your bench on randomly generated data is usually completely unrepresentative
of the actual performance. Most users don't work on random data. There are whole branches of computer science dedicated
towards synthesising useful artificial datasets. The best way to get a reliable benchmark is using real data. Find the queries
that use up the most time. Use anonimisied production data, if possible. Use open-source datasets for common documents like
JSONs.
-
+
Use your brain. Benchmarks are experiments that test our hypotheses. We still need to come up with the hypotheses
and analyse the data. If the distribution doesn't make sense, find out why. Unexpected results must be understood.
-
+
Drive your benchmark with business requirements. It's rare that we want a method to just execute as fast as possible
for all data. The requirements are usually more "this query takes 2s and the users are pissed, make it 1s or less",
or "we need P95 to be at most 200ms". Of course this only matters when you're concerned with the business –
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/03-RefStructs.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/03-RefStructs.razor
index 10d4ffb..f4cadbf 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/03-RefStructs.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/03-RefStructs.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+
slices – Span<T> and Memory<T>;
-
+ ref structs and their rules;
-
+
using Span<T> overloads;
-
+
custom ref structs;
-
+ ref locals and ref returns;
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/04-TheInModifier.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/04-TheInModifier.razor
index 9fa1f6f..2890bca 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/04-TheInModifier.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/04-TheInModifier.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
passing by immutable reference with in;
-
+
when to use to avoid performance penalties.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/05-ArrayPooling.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/05-ArrayPooling.razor
index dd2943a..d367ea8 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/05-ArrayPooling.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/05-ArrayPooling.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+
the core ArrayPool<T>;
-
+
the nicer MemoryPool<T>.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/06-Unsafe.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/06-Unsafe.razor
index 3bc4671..e755af4 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/06-Unsafe.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/06-Unsafe.razor
@@ -12,25 +12,25 @@
In this section we'll cover
-
+ unsafe contexts;
-
+
pinning with fixed;
-
+
pointers;
-
+
unsafe stackalloc;
-
+
function pointers;
-
+ nint and nuint;
-
+
the Unsafe class.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/07-Finalizers.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/07-Finalizers.razor
index d20679e..0e2c83d 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/07-Finalizers.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/07-Finalizers.razor
@@ -12,16 +12,16 @@
In this section we'll cover
-
+
why we should not use finalizers;
-
+
how to not use finalizers;
-
+
how to not use finalizers even when you need a finalizer with SafeHandle;
-
+
just don't.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Assignment-RapidReconnaissance.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Assignment-RapidReconnaissance.razor
index f5abab1..03889cd 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Assignment-RapidReconnaissance.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Assignment-RapidReconnaissance.razor
@@ -23,24 +23,24 @@
Here's what you may and may not do to achieve that.
-
+
You may not change the benchmark, GraphDungeonEvaluatorBaseline, ValueBaseline,
or SprawlingStronghold code.
-
+
You may not change the signature of the static Evaluate method.
-
+
You may not change the equality contract or the primary constructor of Value.
-
+
You may not break any tests in the solution.
-
+
You may change GraphDungeonEvaluator and Value implementations
in any way except for the exclusions above.
-
+
You may change the implementations of other graph algorithms in the solution, like
the TopologicalSort.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Introduction.razor
index 57d44a9..348ea4a 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/09-Performance/Introduction.razor
@@ -25,16 +25,16 @@
- how to reliably benchmark .NET code;
-
+ how to reliably benchmark .NET code;
+
how to profile C# code with Visual Studio (and alternatives);
- more details about Garbage Collection;
- ref struct;
- Span<T> and Memory<T>;
- stackalloc;
- unsafe;
- finalizers.
+ more details about Garbage Collection;
+ ref struct;
+ Span<T> and Memory<T>;
+ stackalloc;
+ unsafe;
+ finalizers.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/00-Synchronisation.razor b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/00-Synchronisation.razor
index 2891ba3..f9a8fb2 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/00-Synchronisation.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/00-Synchronisation.razor
@@ -12,19 +12,19 @@
In this section we'll cover
-
+ lock statement and the Monitor static class;
-
+
nonexclusive locking with semaphores;
-
+
lazy initialisation with Lazy<T>;
-
+
nonblocking synchronisation;
-
+
thread-safety for free!
diff --git a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/01-ConcurrenctCollections.razor b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/01-ConcurrenctCollections.razor
index be744ed..a3a5675 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/01-ConcurrenctCollections.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/01-ConcurrenctCollections.razor
@@ -12,10 +12,10 @@
In this section we'll cover
-
+ System.Collections.Concurrent;
-
+ BlockingCollection as producer-consumer communication channel.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/02-Plinq.razor b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/02-Plinq.razor
index 3b18c4f..2035af1 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/02-Plinq.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/02-Plinq.razor
@@ -12,13 +12,13 @@
In this section we'll cover
-
+
Parallel LINQ;
-
+
partitioners;
-
+
parallel loops.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/03-Channels.razor b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/03-Channels.razor
index f0beb27..2f83082 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/03-Channels.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/03-Channels.razor
@@ -12,7 +12,7 @@
In this section we'll cover
-
+
asynchronous communication.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/Introduction.razor
index 9f3bca9..914e802 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/10-Concurrency/Introduction.razor
@@ -14,13 +14,13 @@
- how to use low-level synchronisation primitives;
-
+ how to use low-level synchronisation primitives;
+
what thread-safe collections are available out-of-the-box;
- synchronous producer-consumer model;
- PLINQ;
- asynchronous producer-consumer model with channels.
+ synchronous producer-consumer model;
+ PLINQ;
+ asynchronous producer-consumer model with channels.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Assignment-Elector.razor b/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Assignment-Elector.razor
index 6782da5..e14a823 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Assignment-Elector.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Assignment-Elector.razor
@@ -130,19 +130,19 @@
The election can be in one of five states:
-
+
NotStarted – just after creation.
-
+
GatheringVotes – ballots are being added to the election.
-
+
GatheredVotes – all ballots are added, waiting.
-
+
CalculatingOutcome – running the algorithm to determine winners of the election.
-
+
CalculatedOutcome – election ended, winners decided.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Introduction.razor
index cc3a001..60a83bd 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/11-Blazor/Introduction.razor
@@ -13,7 +13,7 @@
- what is Blazor and how it works.
+ what is Blazor and how it works.
diff --git a/src/Sorcery/Pages/Teaching/CSharp/Introduction.razor b/src/Sorcery/Pages/Teaching/CSharp/Introduction.razor
index 6047436..ffc2c00 100644
--- a/src/Sorcery/Pages/Teaching/CSharp/Introduction.razor
+++ b/src/Sorcery/Pages/Teaching/CSharp/Introduction.razor
@@ -13,13 +13,13 @@
SQL databases and object-orientation. The desired effect at the end is for the student to:
- have written couple hundred of lines of working C#, gaining practical experience;
- have built a full .NET application for their portfolio;
-
+ have written couple hundred of lines of working C#, gaining practical experience;
+ have built a full .NET application for their portfolio;
+
have gained understanding of C#'s design principles and idiomatic usage of the language, its differences
from other general-purpose languages, and why it's a solid choice for modern development;
- have gained more throughout knowledge of the language and ecosystem than is required for a junior position as a C#.NET developer.
+ have gained more throughout knowledge of the language and ecosystem than is required for a junior position as a C#.NET developer.
@@ -33,19 +33,19 @@
During the course we will cover:
- C# basics and birds-eye view of the .NET ecosystem;
- the C# type system, reference types, value types;
- exception handling, error handling, null-safety;
- generic types in .NET;
- designing classes, structs, records;
- idiomatic C# patterns, writing well-tested, robust code;
- collections and Language Integrated Query;
- functional programming patterns in C#;
- asynchronous programming;
- parallelism and concurrency;
- flexible database access with EntityFramework;
- internals of .NET, garbage collection
- performance-oriented programming in C#
+ C# basics and birds-eye view of the .NET ecosystem;
+ the C# type system, reference types, value types;
+ exception handling, error handling, null-safety;
+ generic types in .NET;
+ designing classes, structs, records;
+ idiomatic C# patterns, writing well-tested, robust code;
+ collections and Language Integrated Query;
+ functional programming patterns in C#;
+ asynchronous programming;
+ parallelism and concurrency;
+ flexible database access with EntityFramework;
+ internals of .NET, garbage collection
+ performance-oriented programming in C#Grading
@@ -54,7 +54,7 @@
The grade is based on two components: microassignments, which are given each week at the end of a module, and the final project.
-
+
Each microassignment is worth 5 points. 2 points are for automated testing, 3 are for code quality.
Assignments are given via GitHub Classroom and you will automatically see the results of automated tests when working on your code.
@@ -78,7 +78,7 @@
The intention is that you shouldn't have to sweat to pass, but to get a good grade you need to put in work.
-
+
Over the course of the semester you're expected to come up with an idea for a project and implement it in C#.NET using the tools you've learnt.
This can be any application you feel like doing, be it a web app in Blazor, a desktop app in WPF, a CLI application doing something interesting,
@@ -88,13 +88,13 @@
points for code quality. In total, you can get at most 50 points for the project.
-
+ UI (10): this can be a GUI, but it can also be a command-line interface.
The only requirement is that it has to be .NET-powered – don't expect points for writing a pure ReactJS app over your .NET API!
-
+ Logic (20): the code that operates on data and gives results to be displayed in the UI. This will be
the business logic of your web app, the rules and scripts of your game, or whatever other functionality you want your project to provide,
@@ -103,13 +103,13 @@
If you write a game, it might be the save file system.
-
+ Tests (10): every part of your logic should be unit tested.
Any additional testing, like integration or end-to-end tests, will count as a bonus.
-
+ Bonus (???): surprise me.
@@ -124,7 +124,7 @@
The project is supposed to be your opportunity to be creative, so there's no artificial limitations.
-
+ Grade
@@ -136,7 +136,7 @@
-
+
If you fail the first term, you will be given a special assignment to complete until September.
It will be a single, big assignment on GitHub Classroom spanning the entire course. The grade is binary: either you pass all tests or not.
@@ -152,7 +152,7 @@
There is no way to regain points for microassignments in the second term.
-
+
You can use the below calculator to get your grade based on the points you currently have.
@@ -173,7 +173,7 @@
-
+ @_calculatorState.Sum
@@ -181,13 +181,13 @@
@{
var color = _calculatorState.MicroassignmentsPassed ? Color.Success : Color.Error;
var text = _calculatorState.MicroassignmentsPassed ? "Passed!" : "Not passed.";
-
+ @text
}
-
+ @GradeToString(_calculatorState.Grade)
diff --git a/src/Sorcery/Shared/BlogBook.cs b/src/Sorcery/Shared/BlogBook.cs
index dd24b16..74560d3 100644
--- a/src/Sorcery/Shared/BlogBook.cs
+++ b/src/Sorcery/Shared/BlogBook.cs
@@ -21,6 +21,7 @@ public BlogBook()
{
new ("simd"),
new ("csharp"),
+ new ("perf"),
},
Description = Pages.Sourcery.Posts.SimdCheatCodesForFreePerformance.Introduction,
ShortDescription = "Discovering the wonderful parallel universe of local parallelism.",
diff --git a/src/Sorcery/Shared/Components/Blogging/BlogPost.razor b/src/Sorcery/Shared/Components/Blogging/BlogPost.razor
index 43b032f..43c02a8 100644
--- a/src/Sorcery/Shared/Components/Blogging/BlogPost.razor
+++ b/src/Sorcery/Shared/Components/Blogging/BlogPost.razor
@@ -1,5 +1,6 @@
@using System.Web;
@using Sorcery.Blogging;
+@using Sorcery.Shared.Components;
@using Sorcery.Shared.Components.Footnotes;
@Title
@@ -25,6 +26,7 @@
@ChildContent
+
diff --git a/src/Sorcery/Shared/Components/ButtonNext.razor b/src/Sorcery/Shared/Components/ButtonNext.razor
index c231995..7f82b8f 100644
--- a/src/Sorcery/Shared/Components/ButtonNext.razor
+++ b/src/Sorcery/Shared/Components/ButtonNext.razor
@@ -1,7 +1,7 @@
-
+ @ChildContent
diff --git a/src/Sorcery/Shared/Components/ButtonPrevious.razor b/src/Sorcery/Shared/Components/ButtonPrevious.razor
index 8e01b93..e1fcf83 100644
--- a/src/Sorcery/Shared/Components/ButtonPrevious.razor
+++ b/src/Sorcery/Shared/Components/ButtonPrevious.razor
@@ -1,5 +1,5 @@
-
+ @ChildContent
diff --git a/src/Sorcery/Shared/Components/CommentBox.razor b/src/Sorcery/Shared/Components/CommentBox.razor
new file mode 100644
index 0000000..21b1f3c
--- /dev/null
+++ b/src/Sorcery/Shared/Components/CommentBox.razor
@@ -0,0 +1,47 @@
+@inject IJSRuntime JSRuntime;
+
+
+
+
+ @if (ThemeInfo?.IsDarkMode is true)
+ {
+
+ }
+ else
+ {
+
+ }
+
+
+@code {
+ [CascadingParameter]
+ protected ThemeInfo? ThemeInfo { get; set; }
+}
diff --git a/src/Sorcery/Shared/Components/Header4.razor b/src/Sorcery/Shared/Components/Header4.razor
new file mode 100644
index 0000000..6b4d9e5
--- /dev/null
+++ b/src/Sorcery/Shared/Components/Header4.razor
@@ -0,0 +1,11 @@
+@ChildContent
+
+
+@code {
+ [Parameter]
+ [EditorRequired]
+ public RenderFragment ChildContent { get; set; } = null!;
+
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary Attributes { get; set; } = new();
+}
diff --git a/src/Sorcery/Shared/Components/LatexRenderer.razor b/src/Sorcery/Shared/Components/LatexRenderer.razor
index 5d19dee..61fe510 100644
--- a/src/Sorcery/Shared/Components/LatexRenderer.razor
+++ b/src/Sorcery/Shared/Components/LatexRenderer.razor
@@ -24,7 +24,7 @@
right = "$",
display = false
}
- },
+ },
throwOnError = true
};
@@ -32,8 +32,12 @@
{
if (firstRender)
{
- var container = await JSRuntime.InvokeAsync("document.getElementById", ContainerId);
- await JSRuntime.InvokeVoidAsync("renderMathInElement", container, _configuration);
+ var container = await JSRuntime.InvokeAsync("document.getElementById", ContainerId);
+
+ if (container != null)
+ {
+ await JSRuntime.InvokeVoidAsync("renderMathInElement", container, _configuration);
+ }
}
}
}
diff --git a/src/Sorcery/Shared/Components/MudNavGroupLink.razor b/src/Sorcery/Shared/Components/MudNavGroupLink.razor
index c2fa5ae..8ae1719 100644
--- a/src/Sorcery/Shared/Components/MudNavGroupLink.razor
+++ b/src/Sorcery/Shared/Components/MudNavGroupLink.razor
@@ -62,13 +62,13 @@
/// The expand Icon that will be used in the collapsed state.
///
[Parameter]
- public string CollapsedIcon { get; set; } = @Icons.Filled.ArrowDropDown;
+ public string CollapsedIcon { get; set; } = @Icons.Material.Filled.ArrowDropDown;
///
/// The hide Icon that will be used in the expanded state.
///
[Parameter]
- public string ExpandedIcon { get; set; } = @Icons.Filled.ArrowDropUp;
+ public string ExpandedIcon { get; set; } = @Icons.Material.Filled.ArrowDropUp;
[Parameter]
public RenderFragment? ChildContent { get; set; }
diff --git a/src/Sorcery/Shared/Components/Quote.razor b/src/Sorcery/Shared/Components/Quote.razor
index 0010d9a..9591dd0 100644
--- a/src/Sorcery/Shared/Components/Quote.razor
+++ b/src/Sorcery/Shared/Components/Quote.razor
@@ -1,6 +1,6 @@