(This page has no text content)
Programming C# 10 Build Cloud, Web, and Desktop Applications Ian Griffiths
Programming C# 10 by Ian Griffiths Copyright © 2022 Ian Griffiths. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisitions Editor: Amanda Quinn Development Editor: Corbin Collins Production Editor: Elizabeth Faerm Copyeditor: Kim Cofer Proofreader: Piper Editorial Consulting, LLC Indexer: WordCo Indexing Services, Inc. Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Kate Dullea August 2022: First Edition Revision History for the Early Release
2021-12-23: First Release 2022-03-28: Second Release 2022-07-12: Final Release See http://oreilly.com/catalog/errata.csp?isbn=9781098117818 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Programming C# 10, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the author, and do not represent the publisher’s views. While the publisher and the author have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the author disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights. 978-1-098-11775-7 [LSI]
Dedication I dedicate this book to my excellent wife Deborah, and to my wonderful daughters, Hazel, Victoria, and Lyra. Thank you for enriching my life.
Preface C# has now existed for around two decades. It has grown steadily in both power and size, but Microsoft has always kept the essential characteristics intact. Each new capability is designed to integrate cleanly with the rest, enhancing the language without turning it into an incoherent bag of miscellaneous features. Even though C# continues to be a fairly straightforward language at its heart, there is a great deal more to say about it now than in its first incarnation. Because there is so much ground to cover, this book expects a certain level of technical ability from its readers.
Who This Book Is For I have written this book for experienced developers—I’ve been programming for years, and I set out to make this the book I would want to read if that experience had been in other languages, and I were learning C# today. Whereas earlier editions explained some basic concepts such as classes, polymorphism, and collections, I am assuming that readers will already know what these are. The early chapters still describe how C# presents these common ideas, but the focus is on the details specific to C#, rather than the broad concepts. Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width Used for program listings, as well as within paragraphs to refer to program elements such as variable or function names, databases, data types, environment variables, statements, and keywords. Constant width bold Shows commands or other text that should be typed literally by the user. In examples, highlights code of particular interest. Constant width italic Shows text that should be replaced with user-supplied values or by values determined by context.
TIP This element signifies a tip or suggestion. NOTE This element signifies a general note. WARNING This element indicates a warning or caution. Using Code Examples Supplemental material (code examples, exercises, etc.) is available for download at https://github.com/idg10/prog-cs-10-examples. If you have a technical question or a problem using the code examples, please send email to bookquestions@oreilly.com. This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example:
“Programming C# 10 by Ian Griffiths (O’Reilly). Copyright 2022 by Ian Griffiths, 978-1-098-11781-8.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. O’Reilly Online Learning NOTE For more than 40 years, O’Reilly has provided technology and business training, knowledge, and insight to help companies succeed. Our unique network of experts and innovators share their knowledge and expertise through books, articles, conferences, and our online learning platform. O’Reilly’s online learning platform gives you on-demand access to live training courses, in-depth learning paths, interactive coding environments, and a vast collection of text and video from O’Reilly and 200+ other publishers. For more information, please visit http://oreilly.com. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local)
707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at https://www.oreilly.com/library/view/programming-c-10/9781098117801/. Email us with comments or technical questions at bookquestions@oreilly.com. For more information about our books, courses, conferences, and news, see our website at http://www.oreilly.com. Find us on LinkedIn: https://linkedin.com/company/oreilly-media Follow us on Twitter: http://twitter.com/oreillymedia Watch us on YouTube: http://http.youtube.com/oreillymedia Acknowledgments Many thanks to the book’s official technical reviewers: Stephen Toub, Howard van Rooijen, and Glyn Griffiths. I’d also like to give a big thank you to those who reviewed individual chapters, or otherwise offered help or information that improved this book: Brian Rasmussen, Eric Lippert, Andrew Kennedy, Daniel Sinclair, Brian Randell, Mike Woodring, Mike Taulty, Bart De Smet, Matthew Adams, Jess Panni, Jonathan George, Mike Larah, Carmel Eve, Ed Freeman, Elisenda Gascon, Jessica Hill, Liam Mooney, Nehemiah Campbell, and Shahryar Saljoughi. Thanks in particular to endjin, both for allowing me to take time out from work to write this book, and also for creating such a great place to work. Thank you to everyone at O’Reilly whose work brought this book into existence. In particular, thanks to Corbin Collins for his support in making this book happen, and to Tyler Ortman for his support in getting this project started. Thanks also to Cassandra Furtado, Deborah Baker, Ron Bilodeau, Nick Adams, Rebecca Demarest, Karen Montgomery, and Kristen Brown, for their help in bringing the work to completion. Thanks also to Kim Cofer
for her thorough and thoughtful copy editing, and Kim Sandoval’s diligent proofreading. Finally, thank you to John Osborn, for taking me on as an O’Reilly author back when I wrote my first book.
Chapter 1. Introducing C# The C# programming language (pronounced “see sharp”) is used for many kinds of applications, including websites, cloud-based systems, IoT devices, machine learning, desktop applications, embedded controllers, mobile apps, games, and command-line utilities. C#, along with the supporting runtime, libraries, and tools known collectively as .NET, has been center stage for Windows developers for over twenty years. Today, .NET is cross-platform and open source, enabling applications and services written in C# to run on operating systems including Android, iOS, macOS and Linux, as well as on Windows. The release of C# 10.0 and its corresponding runtime, .NET 6.0, marks an important milestone: C#’s journey to becoming a fully cross-platform, open source language is now complete. Although open source implementations have existed for most of C#’s history, a sea change began in 2016 when Microsoft released .NET Core 1.0, the first platform fully supported by Microsoft for running C# on Linux and macOS as well as Windows. Library and tool support for .NET Core was initially patchy, so Microsoft continued to ship new versions of its older runtime, the closed-source, Windows-only .NET Framework, but five years on, that old runtime is effectively being retired, now that the cross-platform version has comprehensively overtaken it. .NET 5.0 dropped the “Core” from its name to signify that it is now the main event, but it is with .NET 6.0 that the cross-platform version has really arrived, because this version enjoys full Long Term Support (LTS) status. For the first time, the platform- independent version of C# and .NET has superseded the old .NET Framework. C# and .NET are open source projects, although it didn’t start out that way. In C#’s early history, Microsoft guarded all of its source code closely, but in 2014, the .NET Foundation was created to foster the development of open source projects in the .NET world. Many of Microsoft’s most important C# 1
and .NET projects are now under the foundation’s governance (in addition to many non-Microsoft projects). This includes Microsoft’s C# compiler, and also the .NET runtime and libraries. Today, pretty much everything surrounding C# is developed in the open, with code contributions from outside of Microsoft being welcome. New language feature proposals are managed on GitHub, enabling community involvement from the earliest stages. Why C#? Although there are many ways you can use C#, other languages are always an option. Why might you choose C# over those? It will depend on what you need to do, and what you like and dislike in a programming language. I find that C# provides considerable power, flexibility, and performance, and works at a high enough level of abstraction that I don’t expend vast amounts of effort on little details not directly related to the problems my programs are trying to solve. Much of C#’s power comes from the range of programming techniques it supports. For example, it offers object-oriented features, generics, and functional programming. It supports both dynamic and static typing. It provides powerful list- and set-oriented features, thanks to Language Integrated Query (LINQ). It has intrinsic support for asynchronous programming. Moreover, the various development environments that support C# all offer a wide range of productivity enhancing features. C# provides options for balancing ease of development against performance. The runtime has always provided a garbage collector (GC) that frees developers from much of the work associated with recovering memory that the program is no longer using. A GC is a common feature in modern programming languages, and while it is a boon for most programs, there are some specialized scenarios where its performance implications are problematic, so C# enables more explicit memory management, giving you the option to trade ease of development for runtime performance, but without the loss of type safety. This makes C# suitable for certain
performance-critical applications that for years were the preserve of less safe languages such as C and C++. Languages do not exist in a vacuum—high-quality libraries with a broad range of features are essential. Some elegant and academically beautiful languages are glorious right up until you want to do something prosaic, such as talking to a database or determining where to store user settings. No matter how powerful a set of programming idioms a language offers, it also needs to provide full and convenient access to the underlying platform’s services. C# is on very strong ground here, thanks to its runtime, built-in class libraries, and extensive third-party library support. .NET encompasses both the runtime and the main class libraries that C# programs use. The runtime part is called the Common Language Runtime (usually abbreviated to CLR) because it supports not just C#, but any .NET language. Microsoft also offers Visual Basic, F#, and .NET extensions for C++, for example. The CLR has a Common Type System (CTS) that enables code from multiple languages to interoperate freely, which means that .NET libraries can normally be used from any .NET language—F# can consume libraries written in C#, C# can use Visual Basic libraries, and so on. There is an extensive set of class libraries built into .NET. These have gone by a few names over the years, including Base Class Library (BCL), Framework Class Library, and framework libraries, but Microsoft now seems to have settled on runtime libraries as the name for this part of .NET. These libraries provide wrappers for many features of the underlying operating system (OS), but they also provide a considerable amount of functionality of their own, such as collection classes and JSON processing. The .NET runtime class libraries are not the whole story—many other systems provide their own .NET libraries. For example, there are libraries that enable C# programs to use popular cloud services. As you’d expect, Microsoft provides comprehensive .NET libraries for working with services in its Azure cloud platform. Likewise, Amazon provides a fully featured development kit for using Amazon Web Services (AWS) from C# and other .NET languages. And libraries do not have to be associated with particular
services. There’s a large ecosystem of .NET libraries, some commercial, and some free, including mathematical utilities, parsing libraries, and user interface (UI) components, to name just a few. Even if you get unlucky and need to use an OS feature that doesn’t have any .NET library wrappers, C# offers various mechanisms for working with other kinds of APIs, such as the C-style APIs available in Win32, macOS, and Linux, or APIs based on the Component Object Model (COM) in Windows. In addition to libraries there are also numerous applications frameworks. .NET has built-in frameworks for creating web apps and web APIs, desktop applications, and mobile applications. There are also open source frameworks for various styles of distributed systems development such as high-volume event processing with Reaqtor, or high-availability globally distributed systems with project Orleans. Finally, with .NET having been around for over two decades, many organizations have invested extensively in technology built on this platform. So C# is often the natural choice for reaping the rewards of these investments. In summary, with C# we get a strong set of abstractions built into the language, a powerful runtime, and easy access to an enormous amount of library and platform functionality. Managed Code and the CLR C# was the first language designed to be a native in the world of the CLR. This gives C# a distinctive feel. It also means that if you want to understand C#, you need to understand the CLR and the way in which it runs code. For years, the most common way for a compiler to work was to process source code, and to produce output in a form that could be executed directly by the computer’s CPU. Compilers would produce machine code—a series of instructions in whatever binary format was required by the kind of CPU the computer had. Many compilers still work this way, but the C# compiler does not. Instead, it uses a model called managed code.
With managed code, the compiler does not generate the machine code that the CPU executes. Instead, the compiler produces a form of binary code called the intermediate language (IL). The executable binary is produced later, usually, although not always, at runtime. The use of IL enables features that are hard or even impossible to provide under the more traditional model. Perhaps the most visible benefit of the managed model is that the compiler’s output is not tied to a single CPU architecture. For example, the CPUs used in most modern computers support both 32-bit and 64-bit instruction sets (known respectively, for historical reasons, as x86 and x64). With the old model of compiling source code into machine language, you’d need to choose which of these to support, building multiple versions of your component if you need target more than one. But with .NET, you can build a single component that can run without modification in either 32-bit or-64 bit processes. The same component could even run on completely different architectures such as ARM (a processor architecture widely used in mobile phones, newer Macs, and also in tiny devices such as the Raspberry Pi). With a language that compiles directly to machine code, you’d need to build different binaries for each of these, or in some cases you might build a single file that contains multiple copies of the code, one for each supported architecture. With .NET, you can compile a single component that contains just one version of the code, and it can run on any of them. It would even be able to run on platforms that weren’t supported at the time you compiled the code if a suitable runtime became available in the future. (For example, .NET components written years before Apple released its first ARM-based Macs can run natively, without relying on the Rosetta translation technology that normally enables older code to work on the newer processors.) More generally, any kind of improvement to the CLR’s code generation—whether that’s support for new CPU architectures, or just performance improvements for existing ones—is instantly of benefit to all .NET languages. For example, older versions of the CLR did not take advantage of the vector processing extensions available on modern x86 and x64 processors, but the current versions will now often exploit these when generating code for loops. All code running on current versions of .NET
benefits from this, including components that were built years before this enhancement was added. The exact moment at which the CLR generates executable machine code can vary. Typically, it uses an approach called just-in-time (JIT) compilation, in which each individual function’s machine code is generated the first time it runs. However, it doesn’t have to work this way. One of the runtime implementations, called Mono, is able to interpret IL directly without ever converting it to runnable machine language, which is useful on platforms such as iOS where legal constraints may prevent JIT compilation. The .NET Software Development Kit (SDK) also provides a tool called crossgen, which enables you to build precompiled code alongside the IL. This ahead-of-time (AoT) compilation can improve an application’s startup time. There’s also a whole separate runtime called .NET Native that only supports precompilation, and which is used by Windows Store Apps built for the Universal Windows Platform (UWP). (Be aware that Microsoft has announced that the Windows-only .NET Native runtime is likely to be phased out and replaced by NativeAOT, its cross-platform successor.) NOTE Even when you precompile code with crossgen, generation of executable code can still happen at runtime. The CLR’s tiered compilation feature may choose to recompile a method dynamically to optimize it better for the ways it is being used at runtime, and it can do this whether you’re using JIT or AoT. Managed code has ubiquitous type information. The .NET runtime requires this to be present, because it enables certain runtime features. For example, .NET offers various automatic serialization services, in which objects can be converted into binary or textual representations of their state, and those representations can later be turned back into objects, perhaps on a different machine. This sort of service relies on a complete and accurate description of an object’s structure, something that’s guaranteed to be present in managed code. Type information can be used in other ways. For example, unit test frameworks can use it to inspect code in a test project and discover 2
all of the unit tests you have written. This relies on the CLR’s reflection services, which are the topic of Chapter 13. Although C#’s close connection with the runtime is one of its main defining features, it’s not the only one. There’s a certain philosophy underpinning C#’s design. C# Prefers Generality to Specialization C# favors general-purpose language features over specialized ones. C# is now on its tenth major version, and with every release, the language’s designers had specific scenarios in mind when designing new features. However, they have always tried hard to ensure that each element they add is useful beyond these primary scenarios. For example, a few years ago, the C# language designers decided to add features to C# to make database access feel well integrated with the language. The resulting technology, Language Integrated Query (LINQ, described in Chapter 10), certainly supports that goal, but they achieved this without adding any direct support for data access to the language. Instead, the design team introduced a series of quite diverse-seeming capabilities. These included better support for functional programming idioms, the ability to add new methods to existing types without resorting to inheritance, support for anonymous types, the ability to obtain an object model representing the structure of an expression, and the introduction of query syntax. The last of these has an obvious connection to data access, but the rest are harder to relate to the task at hand. Nonetheless, these can be used collectively in a way that makes certain data access tasks significantly simpler. But the features are all useful in their own right, so as well as supporting data access, they enable a much wider range of scenarios. For example, these additions made it very much easier to process lists, sets, and other groups of objects, because the new features work for collections of things from any origin, not just databases. One illustration of this philosophy of generality was a language feature that was prototyped for C#, but which its designers ultimately chose not to go
ahead with. The feature would have enabled you to write XML directly in your source code, embedding expressions to calculate values for certain bits of content at runtime. The prototype compiled this into code that generated the completed XML at runtime. Microsoft Research demonstrated this publicly, but this feature didn’t ultimately make it into C#, although it did later ship in another .NET language, Visual Basic, which also got some specialized query features for extracting information from XML documents. Embedded XML expressions are a relatively narrow facility, only useful when you’re creating XML documents. As for querying XML documents, C# supports this functionality through its general-purpose LINQ features, without needing any XML-specific language features. XML’s star has waned since this language concept was mooted, having been usurped in many cases by JSON (which will doubtless be eclipsed by something else in years to come). Had embedded XML made it into C#, it would by now feel like a slightly anachronistic curiosity. The new features added in subsequent versions of C# continue in the same vein. For example, the deconstruction and pattern matching features added across the last few versions of C# are aimed at making life easier in subtle but useful ways, and are not limited to any particular application area. C# Standards and Implementations Before we can get going with some actual code, we need to know which implementation of C# and the runtime we are targeting. The standards body ECMA has written specifications that define language and runtime behavior (ECMA-334 and ECMA-335, respectively) for C# implementations. This has made it possible for multiple implementations of C# and the runtime to emerge. At the time of writing, there are four in widespread use: Mono, .NET Native, .NET (formerly known as .NET Core), and .NET Framework. Somewhat confusingly, Microsoft is behind all of these, although it didn’t start out that way. Many .NETs 3
The Mono project was launched in 2001, and did not originate from Microsoft. (This is why it doesn’t have .NET in its name—it can use the name C# because that’s what the standards call the language, but back in the pre-.NET Foundation days, the .NET brand was exclusively used by Microsoft.) Mono started out with the goal of enabling Linux desktop application development in C#, but it went on to add support for iOS and Android. That crucial move helped Mono find its niche, because it is now mainly used to create cross-platform mobile device applications in C#. Mono now also supports targeting WebAssembly (also known as WASM), and includes an implementation of the CLR that can run in any standards- compliant web browser, enabling C# code to run on the client side in web applications. This is often used in conjunction with a .NET application framework called Blazor, which enables you to build HTML-based user interfaces while using C# to implement behavior. The Blazor-with-WASM combination also makes C# a viable language for working with platforms such as Electron, which use web client technologies to create cross-platform desktop applications. (Blazor doesn’t require WASM—it can also work with C# code compiled normally and running on the .NET runtime; this is the basis for .NET’s Multi-platform App UI (MAUI), which makes it possible to write a single application that can run on Android, iOS, macOS, and Windows.) Mono was open source from the start, and has been supported by a variety of companies over its existence. In 2016, Microsoft acquired the company that had stewardship of Mono: Xamarin. For now Microsoft retains Xamarin as a distinct brand, positioning it as the way to write cross- platform C# applications that can run on mobile devices. Mono’s core technology has been merged into Microsoft’s .NET runtime codebase. This was the endpoint of several years of convergence in which Mono gradually shared more and more in common with .NET. Initially Mono provided its own implementations of everything: C# compiler, libraries, and the CLR. But when Microsoft released an open source version of its own compiler, the Mono tools moved over to that. Mono used to have its own complete implementation of the .NET runtime libraries, but ever since Microsoft first released the open source .NET Core, Mono has been depending
Comments 0
Loading comments...
Reply to Comment
Edit Comment