Sign in

Joseph Musser

@jnm2.bsky.social
78 followers 128 following 58 posts

Senior software engineer at Microsoft, C# language design member, former 7-year Microsoft MVP

PostsRepliesMedia
Joseph Musser @jnm2.bsky.social · 01/04/2026
We're also increasingly finding that people are favorable to, and unbothered by, small breaking changes in the language. Users control when to take the break (on upgrading their project) and there are often automated fixers to make it more seamless at that point. A success story is `field`.
000
Joseph Musser @jnm2.bsky.social · 01/04/2026
The language is more willing to introduce breaking changes that are limited to the use of a very recent new language feature, because its uptake is small for a certain window of time.
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
It was in fact a breaking change to introduce `[with(x), y]`, but this was deemed safe enough given the naming convention is uncommon and given that we could find no examples of this in the code we had access to search. So the next step is putting it out in preview and listening for serious breaks.
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
In short, direct child syntax doesn't need to maintain the meaning that it would when divorced from the parent syntax. But there is no similar direct-child-syntax relationship in play in the `with(x)[y]` example.
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
Likewise, you need `[(with(x))]` if you want to nest a `with(x)` standalone expression. This is also consistent with how interpolation holes define a special meaning for colons, such that you need to parenthesize in order to use standalone constructs that involve a colon: `$"{(a ? b : c)}"`
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
Because it's a direct child syntax element, we can make up new rules however we like without stepping on existing concepts. A direct example of this is spreads. `[..x]` uses `..` to mean "spread" not "range expression." You need `[(..x)]` if you actually want to nest a `..x` standalone expression.
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
That is a great observation. To us, these are not the same type of conflict. Redefining what `with(x)[y]` means would be taking an existing unrelated syntax and redefining it. In contrast in `[with(x), y]`, the `with` is a direct child syntax element of the collection expression.
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
(replied under bsky.app/profile/moke...)
010
Joseph Musser @jnm2.bsky.social · 01/04/2026
Generally we're fine with having to scroll to the start of an expression to understand it. If you somehow landed in the middle, you'll need to do that anyway with most very long expressions. The thought is that you'll almost always be catching the start of the expression before scrolling deep in.
000
Joseph Musser @jnm2.bsky.social · 01/04/2026
Even though we could introduce a weird inconsistency here where `with(comparer)` and `with(comparer) [` meant vastly different things, it really breaks the nice composition that C# has today, where `[` after a primary expression means that you're indexing into it, regardless of what that expr is.
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
It's thorny- thorny paths everywhere. `with(comparer)` by itself must mean a method call. `with(comparer) [1, 2, 3]` already has a valid syntactic meaning in the language, which is to call an indexer with three arguments on the returned value from a method called `with`.
110
Joseph Musser @jnm2.bsky.social · 01/04/2026
We thought we'd hear more interest, but put it off when the interest didn't materialize, because spreads and fixed-size arrays raise a thorny question. We'd love to hear everyone's use case at github.com/dotnet/cshar... which will help prioritize and design the feature!
010
Joseph Musser @jnm2.bsky.social · 01/04/2026
Generally, we hope you enjoy the language, and please keep the great feedback coming about what you like and don't like and why!
010
Joseph Musser @jnm2.bsky.social · 01/04/2026
This feature has been through multiple years of receiving and weighing incoming community feedback like yours above. A more thorough writeup of the path that led us here is at github.com/dotnet/cshar....
100
Joseph Musser @jnm2.bsky.social · 01/04/2026
Finally, it is quite intuitive that `[` signifies the start of a collection expression and `]` signifies the end. You don't have to look far ahead to understand what's happening in the order that it's happening.
210
Joseph Musser @jnm2.bsky.social · 01/04/2026
On the other side of the dilemma, we weren't willing to subvert C#'s left-to-right evaluation order. Additionally, we weren't willing to deal with the developer confusion of evaluating a spread expression without immediately enumerating it before moving on to the next expression.
110
Joseph Musser @jnm2.bsky.social · 01/04/2026
and create the collection instance, then copy over the contents of 'x', 'y', and 'z' from the temporary collection to the final collection. This is a hidden cost that would fundamentally subvert the value proposition of collection expressions being comparable in performance to handwritten code.
110
Joseph Musser @jnm2.bsky.social · 01/04/2026
It would require the compiler at runtime to evaluate 'x', 'y', and 'z' while no collection instance has yet been created- so their contents must all be stored in a temporarily allocated, arbitrarily large temporary collection. Then we get to the 'comparer' expression at the end and can instantiate
110
Joseph Musser @jnm2.bsky.social · 01/04/2026
Why not outside the end? Syntax such as `[x, y, .. z] with(comparer)` faces a terrible dilemma. C# is a language with a left-to-right evaluation order. In that syntax, the 'comparer' expression is not evaluated until after 'x', 'y', and 'z' expressions. That's backwards for building a collection!
110
Joseph Musser @jnm2.bsky.social · 01/04/2026
Why not outside the front? Syntax such as `new(comparer) [x, y, .. z]` is problematic because `new` currently means you're calling a ctor. But we need something that feels fitting when the target collection type is an interface or uses a static Create method instead of a ctor.
220
Joseph Musser @jnm2.bsky.social · 01/04/2026
@moke.me We get it! We searched long and hard for a syntax that didn't have terrible drawbacks. What we landed on is a syntax that is the least bad given our goals, and suitable for the very occasional escape hatch that it is.
110
Joseph Musser @jnm2.bsky.social · 24/01/2026
The wait is almost over! I'm excited to be joining Microsoft Fabric as a senior software engineer. I'm grateful for this opportunity to tackle new challenges together with an amazing team, stretching the limits of .NET build technology! I'll also continue as a member of the C# language design team.
092
Joseph Musser @jnm2.bsky.social · 13/01/2026
I wanted to keep it simple until some motivating cases came to light. I would love to hear some!
000
Joseph Musser @jnm2.bsky.social · 13/01/2026
If C# had a fullnameof operator, would it suit your needs if it worked as shown in this image? Would you let us know? github.com/dotnet/cshar...
A table showing various inputs and outputs of a proposed fullnameof operator:

fullnameof(System.Collections.Generic) 	"System.Collections.Generic"
fullnameof(List<>) 	"System.Collections.Generic.List<>"
fullnameof(global::System.Func<>) 	"System.Func<>"
fullnameof(someExtern::SomeType) 	"SomeType"
fullnameof(ImmutableArray<>.Builder) 	"System.Collections.ImmutableArray<>.Builder"
fullnameof(ImmutableArray<>.Builder.Capacity) 	"System.Collections.ImmutableArray<>.Builder.Capacity"
fullnameof(List<int>) 	❌ Not permitted
fullnameof(TSomeTypeParameterInScope) 	❌ Not permitted
fullnameof((int A, int B)) 	❌ Not permitted
fullnameof(int[]) 	❌ Not permitted
fullnameof(int*) 	❌ Not permitted
fullnameof(int?) 	❌ Not permitted
fullnameof(string?) 	❌ Not permitted
100
Joseph Musser @jnm2.bsky.social · 08/01/2026
Being a library author is complicated in so many ways, I totally get the frustration of setting out to do something fun and learning that it explodes into something more nuanced with a longer time horizon than expected!
010
Joseph Musser @jnm2.bsky.social · 08/01/2026
It all depends on whether the library author wants people to start moving sooner, at the cost of having two APIs. I've been in scenarios where I decided one way and scenarios where I decided the other way!
110
Joseph Musser @jnm2.bsky.social · 08/01/2026
100
Joseph Musser @jnm2.bsky.social · 08/01/2026
One way I have dealt with this dilemma as a library author has been to multitarget my library to both 8 and 10, and use `#if NET10_0_OR_GREATER` so that the API only exists when my consumer targets .NET 10 with C# 14, and I can also have the option to give a fallback API or keep the original API.
010
Joseph Musser @jnm2.bsky.social · 08/01/2026
C# 13 also had this potential: the "allows ref struct," "ref struct interfaces," and "overload resolution property" features require C# 13 to be consumed. If used in public API, C# 12 can't even start to understand what's going on. This is a typical state of things.
100
Joseph Musser @jnm2.bsky.social · 08/01/2026
This was the case with records, Default Interface Members, generic math, ref structs, task-like types... and every new language feature that introduced a public API concept that didn't exist in the prior language version, all the way back to C# 2.
100
Joseph Musser @jnm2.bsky.social · 08/01/2026
It's a real dilemma which is par for the course with any brand new language concept that shows up in public API. The older compiler predates the concept, so it won't be able to figure out whats going on when something so new is used.
100
Joseph Musser @jnm2.bsky.social · 08/01/2026
You're right, I misspoke! Extension indexers have not been implemented yet. github.com/dotnet/rosly...
github.com
Test plan for "extension indexers" feature · Issue #81505 · dotnet/roslyn
Spec: https://github.com/dotnet/csharplang/blob/main/proposals/extension-indexers.md Championed issue: dotnet/csharplang#9856 Compiler LangVer round-tripping DefaultMemberAttribute ref analysis CRE...
020
Joseph Musser @jnm2.bsky.social · 08/01/2026
This is pretty much the same dilemma as when Nullable Reference Types came out and using langversion 8 with older target frameworks was unsupported. Or maybe more like the hard limit when Default Interface Members came out and any library that used them required the latest runtime.
000
Joseph Musser @jnm2.bsky.social · 08/01/2026
Nor has anyone proposed, for instance "extension attributes." So "extension everything" just means "a design space for all the rest of the member kinds." That new design space is afforded by the new extension block syntax.
000
Joseph Musser @jnm2.bsky.social · 08/01/2026
It doesn't let you do things like add an interface implementation. That turns out to be incredibly tricky both to implement and to design. For example, the assembly defining such an extension may load later on than the type that it extends. Now do `is IAddedInterface` checks change behavior?
100
Joseph Musser @jnm2.bsky.social · 08/01/2026
It's because it expands the concept of extensions to cover all the member kinds, not just extension instance methods. So far extension static methods, extension properties and static properties, extension indexer, and extension (non-conversion) operators have been implemented, and more are coming.
200
Joseph Musser @jnm2.bsky.social · 07/01/2026
I believe you can foreach over nullable value-typed collections without explicitly writing .Value or unwrapping by other means. The null check still needs to be done before iterating.
010
Joseph Musser @jnm2.bsky.social · 07/01/2026
There's a superb writeup by @stephentoub.bsky.social at devblogs.microsoft.com/dotnet/perfo...! Updates to it in 9 and 10 too: devblogs.microsoft.com/dotnet/perfo... and search in page for the first hit of "frozen collections" devblogs.microsoft.com/dotnet/perfo...
020
Joseph Musser @jnm2.bsky.social · 01/01/2026
Today was my last day at Technology Solutions Associates, after nearly thirteen years total and seven years as a partner. Signing out for the last time is quite a feeling. I'm grateful for the opportunities I had with them, and ready for the next adventure in 2026. Stay tuned!
050
Joseph Musser @jnm2.bsky.social · 28/11/2025
Ever wanted to write `((a, b)) =>` to deconstruct lambda parameters in C#? I've created a proposal and am co-championing the feature now. Discussion: github.com/dotnet/cshar... Proposal: github.com/dotnet/cshar...
0121
Joseph Musser @jnm2.bsky.social · 22/11/2025
I didn't see null refs mentioned yet:
ref var x = ref System.Runtime.CompilerServices.Unsafe.NullRef<object>();

if (x == null)
{
}
120
Joseph Musser @jnm2.bsky.social · 05/10/2025
I've added a 360° node viewer to my Myst IV Asset Explorer (github.com/jnm2/MystIVA...)! Rendering a skybox with SkiaSharp in Avalonia, from 3D projection first principles, was satisfying. Looking around at the final result with its layer transparency is surreal.
App screenshot showing 360° node viewer
010
Joseph Musser @jnm2.bsky.social · 17/09/2025
I made an app to browse and extract #Myst IV game assets, and play audio assets with gapless looping using #dotnet and #AvaloniaUI: github.com/jnm2/MystIVA... It hits a nostalgic spot to have original game music instead of the official soundtrack's remix; I grew up playing the Myst games.
App screenshot showing in-app browsing panes and audio player
030
Joseph Musser @jnm2.bsky.social · 17/09/2025
Registered for the 2026 #mvpsummit! Looking forward to flying out and seeing everyone!
020
Joseph Musser @jnm2.bsky.social · 22/05/2025
That's very exciting, thanks for listening! And yes, it's a breath of fresh air to use. Feels well thought through at every turn.
000
Joseph Musser @jnm2.bsky.social · 22/05/2025
@patriksvensson.se I'm finally getting into Spectre.Console and it's spectactular, thank you! One thing that's giving me cold feet though is that there's no support for named arguments/required options. It seems to me that named arguments are just as important in CLI scripting as in C#.
110
Joseph Musser @jnm2.bsky.social · 09/05/2025
I'm losing the feeling of "what's happening is bad" that used to pop up for me when facing difficulties. Difficulties are the pathway to heart and soul and life and flow.
000
Joseph Musser @jnm2.bsky.social · 20/01/2025
Discussion for a proposal I'm working on: Target-typed static member lookup github.com/dotnet/cshar...
Summary

This feature enables a type name to be omitted from static member access when it is the same as the target type.

This reduces construction and consumption verbosity for factory methods, nested derived types, enum values, constants, singletons, and other static members. In doing so, the way is also paved for discriminated unions to benefit from the same concise construction and consumption syntaxes.

type.GetMethod("Name", .Public | .Instance | .DeclaredOnly); // BindingFlags.Public | ...

control.ForeColor = .Red;          // Color.Red
entity.InvoiceDate = .Today;       // DateTime.Today
ReadJsonDocument(.Parse(stream));  // JsonDocument.Parse

// Production (static members on Option<int>)
Option<int> option = condition ? .None : .Some(42);

// Production (nested derived types)
CustomResult result = condition ? new .Success(42) : new .Error("message");

// Consumption (nested derived types)
return result switch
{
    .Success(var val) => val,
    .Error => defaultVal,
};
050
Joseph Musser @jnm2.bsky.social · 14/12/2024
No, but this looks amazing! What is it doing differently from 'nuget why' that causes the binlog viewer to show < 47k lines of output for this? Is it excluding deeper-nested packages if the package is already present at a level nearer to the project?
100
Joseph Musser @jnm2.bsky.social · 14/12/2024
There should be a word for the feeling of wrapping up from being ten layers deep into something, specifically finishing a bunch of tasks in a row as you unwind your stack, which you had been holding in memory. There's something satisfying about hitting all of them on the way out.
020