Youre already a decent C# programmer - but you know theres more to software than writing the code. You know you have to make dozens of little decisions in your day to day work: about performance trade-offs, data structures, and OO design. And youd like to deepen your knowledge in a range of areas to help you make better decisions.
|About the instructor: Jonathan Worthington |
Jonathan has a deep knowledge of the structure of language development, as well as a broad understanding of programming paradigms and “best practices.” Jonathan is also a very active open source contributor, and a popular speaker at a range of international workshops and conferences.
This course assumes that participants are able to follow and write code using modern C# language features, including Linq and lambda expressions. Taking our C# Intermediate course is suitable preparation, otherwise you should have at least the skills covered by the C# Intermediate course.
The environment we build for
Its important to understand what were running on, and what were building in terms of. This section takes a deep dive down to the hardware, and then back up through the .NET CLR.
- CPU essentials: pipelining, micro-ops, branches, data dependencies, instruction level parallelism
- Memory essentials: virtual memory, CPU caches, cache lines
- The nature of .Net IL and JIT compilation
- The cost of calls: inlining, interface call optimization, lambdas
- Cost of field accesses
- Cost of exceptions
Garbage Collection and Resource Management
Programs need to store information as they execute. Managing memory is a decades-old problem. The CLR offers garbage collection, which takes away many potential opportunities for mistakes - but is certainly not magical. In this section its behavior and expectations are considered, along with the need to manage things that have limited lifetime, but are not good to leave to the GC.
- Exploring the stack
- Exploring the heap
- Value types vs. reference types
- The challenge garbage collection takes on
- The illusion of resizing
- Boxing and unboxing
- The effect of generational GC
- The large object heap
- Local to heap promotion
- The disposable pattern
Selecting Data Structures
There are a huge number of built-in data types in the .Net framework. Many are useful, but rarely used. Furthermore, there are a number of data structures not found in the class library that are incredibly useful, can be built with relative ease or found on NuGet, and are widely recognized and formalized. Choosing appropriate data structures is a huge part of effective program design.
- A quick guide to time complexity
- A small guide to space complexity
- The importance of constant factors
- Choosing appropriate numeric types
- List vs. Array
- When to turn to linked lists, queues, and stacks
- Dictionaries, and making objects suitable for use as keys
- When bits are enough
The idea of objects has been around for decades, yet understanding of their power is still very unevenly distributed. Programming languages giving the illusion that classes are really just containers for procedures or useful for building record types has not helped matters. In this section well look at how objects can be used more effectively, and how understanding messaging is at the heart of good OO design.
- The origins of object orientation
- Encapsulation, messaging, and late binding
- Method-first object design
- The importance of invariants
- Introducing late binding
- Tell, dont ask
- Properties: the good and the bad
- OO and performance
Mutability, Immutability, and what lies between
For years, imperative programming - centered around manipulating state - has been contrasted with functional programming - centered around computing values. The former is sometimes said to perform better and be easier to learn, while the latter is said to produce code that is easier to reason about, compose, and parallelize. Both have something to offer.
- Mutability: the good and the bad
- Immutability: the good and the bad
- Value objects
- Study: Linq queries vs. imperative code
- The immutable collections, and when to consider them
- Persistent data structures
Extensibility and Composability
Over time, needs change. We have to grow our software, and wed like to try and minimize risk as we do so. Since changing working code is risky, we tend to prefer designs that allow extension without modification. We sometimes also need designs that let us compose building blocks together in a safe and simple way. This section considers some strategies for achieving these goals.
- Examples of extensibility
- Examples of composability
- Inheritance vs. composition
- Inversion of control
- A composable approach
Coping with, and tolerating, failures
Our software often must work with inherently unreliable components. Networks can fail, we can be handed corrupt data, databases can pick us as transaction deadlock victims, and so forth. How can we cope?
- Sources of failure
- Exceptions: when to use them and when to avoid them
- Domain exceptions
- Validation: domain and UI
- Kinds of exception handler
- Transient failures and retries
- Persist stuff early
Working with time
Time is a gnarly domain. Humans talk plenty about dates and times, but often without precision and relying on context to resolve ambiguities. To make things worse, the .Net date/time API makes it all to easy for these ambiguities to make their way into software - often leading to difficult bugs. How can we do better?
- Instantaneous time and elapsed time
- DateTime and TimeSpan
- Local times, timezones, and DST
- The pain of DateTime.Now
- Floating dates and times
- Sending messages to the future
Measuring and logging
Feedback loops are essential to a designer. These can occur at many levels. Key to all of them is ensuring the data obtained is reliable and complete enough.
- Feedback cycles and staying on course
- Benchmarking and its pitfalls
- Profiling time and memory
- Performance counters
- A few thoughts on logging
After looking at a bunch of ideas for producing good designs, well spend a little time considering some problems to be on the watch for.
- Changes touching many places in the code
- ThingyManager, DoodahService, getters/setters everywhere, etc.
- Comment deodorant
- Surprise mutation
- Repeated mistakes