Convenient options are available for almost every task in life, from getting a ride to the airport to writing code. Convenience is the idea that a great solution is available when you want it and that it works for you. As designers of the .NET platform, we aim to provide convenient solutions for many tasks and to improve the convenience of writing apps with each new release.
This post kicks off a new series, exploring convenient solutions to common tasks. Productivity, performance, security, and reliability are hallmark design points of the .NET platform. We described them in detail in our recent Why .NET? post. Stephen Toub also published his annual performance post, Performance Improvements in .NET 8. This post (and the ones that will follow) explores the ideas and features discussed in those other posts in terms of convenient solutions. You’ll see a combination of high-level utility APIs that offer a nice balance of those design points and lower-level APIs that enable you to achieve a different balance per your needs.
The next posts go into much more detail on specific API families, with a lot of code and performance numbers, to fully explore these convenient solutions.
- The Convenience of System.Text.Json
- The Convenience of System.IO
- Wrathmark: An Interesting Compute Workload (guest post)
Let’s start the series with a more general exploration of how the .NET platform delivers on convenience.
Convenience is a spectrum
I like using the terms “convenience” and “control”, to describe the two ends of the “convenience spectrum”. Convenience is descriptive of the experience of writing code and control of your ability to define its behavior.
The most convenient code is compact and straightforward, often with at most a few options to vary behavior (as “choice” is itself a complexity). File.ReadAllText()
is a good example. It returns the contents of a (text) file as a string
that you can read and process. The lowest-level code enables a lot of flexibility, control, and performance optimization, but requires more careful use. Looking at you, File.OpenHandle()
and RandomAccess.Read()
, which together expose an operating system handle with very little getting in your way to read through a file (as bytes) with maximum performance.
I’m going to show you a couple lists of APIs. It’s OK if they don’t look familiar. These APIs start with the most raw concepts and formats (the highest degree of control) and end with the most packaged and refined concepts (the most convenient).
Convenience spectrum for reading a text file:
- Most control:
File.OpenHandle
+RandomAccess.Read
- More convenient:
File.Open
+FileStream.Read
- Even more convenient:
File.OpenText
+StreamReader.ReadLine
- Even more convenient:
File.ReadLines
+IEnumerable<string>
- Even more convenient:
File.ReadAllLines
+string[]
- Most convenience:
File.ReadAllText
+string
Convenience spectrum for reading JSON text:
- Most control:
Utf8JsonReader
+Pipelines
orStream
- More convenient:
JsonDocument
+Stream
- Even more convenient:
JsonSerializer
+Stream
- Most convenient:
JsonSerializer
+string
Note: The APIs are listed as the primary API + their most likely companion API or type.
A key takeaway is that there is no clear break between convenient and control patterns in these lists. The end of one convenience pattern overlaps with the start of the next control pattern. One person’s convenience is another’s control. That’s the definition of a spectrum.
Convenience starts with choice
You might wonder why we need all these APIs. They all do the same thing, right? The first is that each of these options is the right tool for the job in different circumstances and is convenient for that circumstance. In fact, the .NET developer community consistently requests a broad sprectrum of APIs from us and we’re happy to deliver them. The second is that we had to build the low-level APIs in order to make the high-level ones. It’s a lot like towers of lego blocks. In theory, we could have exposed only the high-level APIs by making all the low-level ones private, but that’s neither desirable nor practical in the general case.
Some developer stacks primarily expose high-level APIs that are built on native code libraries, but are missing useful lower-level APIs. The native code libraries are often written in a way that makes it impractical to expose the lower-level APIs to a managed language, so they are not. That’s quite limiting. With .NET, we have a strong philosophy that the library functionality we build should be written in C#, which means that both high- and low-level APIs are available for you to use. It also means you can read the code of all the APIs you use in C# (on GitHub), like the File
class.
Of course, there are places where we haven’t exposed all of the layers; every new API we expose is something we’ll have effectively forever, and requires design and direct testing and maintenance and documentation and compatibility constraints and so on. We’re thus selective in which layers we expose when, and are constantly re-evaluating whether additional support should be exposed. The previously mentioned File.ReadAllText
, for example, has been around for many, many years, whereas the cited RandomAccess.Read
was only recently introduced. Our long-term trend has been to make lower-level APIs available where there is a compelling case.
Convenience enables collaboration
The .NET libraries exposes a broad set of functionality for you to use. In many cases (like with the File
type), much of the related functionality is exposed in one place and designed to work as a larger coherent system. That means you can use more convenient APIs in one part of your code and higher-control APIs elsewhere and it can all be made to work together, nicely.
“Hey … I’m going to be writing this data to a
Stream
with APIs that give me the control we need for our service. You can useStreamReader.ReadLineAsync
to read it. If that doesn’t work, I’ll expose anIAsyncEnumerable<string>
for each line and you can useawait foreach
as a streaming solution. Either option works for me. I love how straightforward all of these options are. It is super easy to connect our code together and it’s all super fast and convenient.” — .NET dev at ACME Solutions.
Developers working in teams can make different (and equally good) convenience choices at different layers within a larger codebase, with straightforward patterns to connect those layers.
I’m in control
Yes, yes. The point here isn’t to pick a point on this spectrum and stick to it for all the code you write. Instead, the intent is to select APIs that satify the requirements of the algorithm at hand, even if you have the skills to write more challenging code that may be better on some metric (which may or may not matter). The person who maintains your code next might not have your same skills and may (incorrectly) conclude the pattern you chose is a requirement when its not.
We use convenient APIs in some places in .NET libraries, even though they are not the maximum speed. They makes the code small, simple and easy to understand and that can be more valuable than maximum speed.
That’s what one of our architects had to say about our approach to our codebase, even in a team dedicated to high performance. We like to write convenient code whenever we can. We’d rather focus our efforts on building more features and optimizing APIs that are likely to get called in a hot loop.
The other side of the coin is that the more efficient the convenience APIs are, the more we’ll be able to use them without concern in our codebase. It makes the team as a whole more efficient. We try to make convenience APIs as efficient as possible within the confines of what the shape of the API allows.
Breaking the spectrum
There are a few cases where a single API covers the majority of use cases. This only happens when an API with a simple contract is an absolute workhorse and is required by a lot of scenarios.
The string
class APIs are a key example. IndexOf
and IndexOfAny
are two favorites. We use these APIs pervasively in the .NET platform and they are used just as much by .NET developers. You can see how many PRs have targeted those APIs.
Many of the IndexOf{Any}
calls are actually on spans now, rather than direct calls to string.IndexOf{Any}
. While the spans are frequently pointing into strings, these APIs often operate on slices (after calling string.AsSpan
, internally).
This family of APIs have been improved a lot, using multiple techniques to improve performance. For example, these APIs uses vector CPU instructions to search for search terms in a string. In .NET 8, support for AVX512 was added. That’s not yet relevant for most hardware, however it means that IndexOf
will be ready for newer hardware when you’ve got it.
We’ll dive into IndexOfAny
in much more depth in System.IO post. It’s a great API.
Closing
The .NET team has a “big tent” philosophy. We want every developer to find APIs that are approachable and suitable. If you are new to programming, we’ve got APIs for you. If you are more familiar with low-level APIs, we’ve got APIs that are likely familar.
I’m looking forward to sharing some in-depth analysis and exploration of the convenience spectrum in upcoming posts and hope that it leads to an an interesting discussion. If anything, this exercise has given me the insight on how much I appreciate the spectrum of these APIs. Perhaps our documentation should be updated to describe each topic area in terms of this specturm.
Thanks to David Fowler, Jan Kotas, and Stephen Toub for their help contributing to these posts.
You can keep up to date with this series by subscribing to the Convenience of .NET tag feed in your favorite RSS reader or subscribe to the entire blog via email below.
I am a long time Linux guy always doing backend work. I came from C (yes, for backend workloads ) via Python3 and a lot of Java to .NET Core, pushed by the tech decision the team made I currently work in.
Our code runs in Linux containers on K8s. Four years ago we've been a green field.
I admit: I absolutely love it. The language and .NET Core as a framework which does not try...
How Microsoft doing change's in c# makes c# worst language , Linq and Task , await was awesome feature but current change's useless , it's not necessary to introduce changes in every release. People using c# for simplicity , fundamental change's are totally worst. Java is not doing any stupid change's.
From many release's I didn't see any revolutionary feature instead of this you guys are playing with syntax.
What is need of this below...
WHAT IS THE WAY TO EXPLAIN TO YOU.
WE NEED LONGER LTS SUPPORT.
THESE “FANCY” STUFF IS NOT NEEDED
WE ARE STILL STUCK WITH 4.7 DUE TO LACK OF SUPPORT FOR EXAMPLE I CLICKONCE OR WINFORMS, WCF
So you don't like the (new) .NET (core) LTS release cycle periods based on your experience with the old .NET Framework (the one bound to Windows only).
If you were already on those .NET (core) LTS releases you would also know, they are generally pretty easy to upgrade to from other .NET (core) releases, compared to the past 4.x .NET Framework, where you are currently.
It's your own choice if you want to support what is today...
@Miguel I have a WPF application that uses WCF for communicating with some external services we can not change, and is deployed with ClickOnce. This year was migrated from .NET Framework 4.8 to .NET 7 on Windows and is working as expected.
I usually don't engage in these types of public discussions, but in my experience, most of the criticism in this thread is unwarranted.
This is my experience with .NET:
I've been working with .NET since 1.0 (yes, I'm that old) and am currently working on a fairly large .NET 7 codebase (400+ projects).
We migrated from .NET Framework 4.8 to .NET Core 3.0 fairly easily and have upgraded to new versions ever since with very little...
Looking forward when .NET will adopt gRPC to use as easy as HTTP now. I'm very familiar with network stack but some gRPC inconveniencies makes me cry :) Also looking forward when WinUI 3/Windows App SDK will reach the quality of WPF and Winforms, for now to write pretty simple Desktop app is like a chalenge to fight with bugs and crashes. I won't report them because I have no enought freee time to complete...
My Dell laptop was manufactured in 2021, but I ran the cpu-z app and it does say that it's i7-1185G7 Processor supports the AVX-512 extensions. I ran a quick .NET 8 RC1 console app to check 'Vector512.IsHardwareAccelerated' and it was returning 'True'. So I'm excited to try out the .NET 8.0 support for these AVX-512 extensions.
And I look forward to seeing the other blog posts in your 'Convenience of .NET'. It's interesting to see...
I've been using all .NET versions professionaly from day 1 and I find it very convenient (build a web app for Windows and Linux so easily is what I call convenient for example).
There's one thing though that seems to be less and less convenient: deployment of apps in certain cases. This has sometimes become a nightmare, especially when .NET is mixed with other technologies, like the App Store, framework-dependent, single file only, AOT, publish config,...
Everyone speaks from their own interests. However, the times will eliminate some former vested interests.
Many of the accusations in the comments section seem a bit funny at this point in time. They only see the difficulty of migration (and yet no one is pushing them with a gun) and only hope that there is something that can remain technically unchanged (or that they can be protected from change). In fact NFX is still maintained and...
Great post. It would be perfect if dotnet's official documentation had a toggle button or something for filtering --- that we could turn on and then be presented with only the most convenient of APIs available in a particular type or library -- without the distraction of going through lower level apis. It will save tons of time while rapid prototyping apps. Call the mode some good suggestive name , like Rapid...
I just wanted to leave a comment as I also noticed how many people here seem to be complaining about .net core.
As someone who really only started working with .NET a few years before .NET Core, the transition to core has made my life so much easier, asp.net core in particular was a huge improvement in my opinion. The amount of screwing around I used to have to do with web.config files and confusing differences...