142 comments

[ 4.0 ms ] story [ 215 ms ] thread
For context, Anders is the creator of C# and TypeScript:

https://en.wikipedia.org/wiki/Anders_Hejlsberg

Interesting - never heard of him, that I can remember anyway, over my career my three favorite programming languages were (in order from oldest to newest) - Turbo Pascal, C# and now Typescript - and apparently he created all three.
He certainly knows how to create programming languages that last.
I had the opportunity to hear him speak about language design at an MS Build conference and was very nearly actually fanboying; my coworkers were more than a little bemused.
He also did Delphi, and you can actually see quite a bit of it in the original design of C#.

Even more so when you look at stuff like WinForms - someone coming from VCL would see many familiar things.

And also the author of Delphi and Turbo Pascal.
loved turbo pascal a long time ago - got my professional start there - but I will forgive him for Delphi anyway :>)
What did you use between TurboPascal and C#? o-O
VB/VB6, with some Clipper and Foxpro mixed in.

Switched to c# instead of picking up vb.net - in retrospect a good call.

Classic progression - some of us came from VB Classic to C# others from C++ (at least in the Windows world). Now we're all one big happy family.
Even though I grew up with C as a kid, I still prefer VB.Net over C# as I believe the code is far more human-readable and less prone to technical errors - BUT, there is just no support for VB.Net. I got stick of having to convert code snippets I would find and this year I finally bit the bullet and moved to doing everything in C#.
Similar route here. I went from VB classic to JScript on the server (and client, so was doing isomorphic JavaScript 19 years ago!) and then to C3 a few years later.
Well I used VB4-6 and Delphi 5-6 extensively, and my experience was Delphi was lightyears ahead of VB, and I say that as someone who was deeply familiar with both and made heavy use of Dan Appleman's Visual Basic Programmer’s Guide to the Win32 API.

It sounds like you were maybe using it at a fairly surface level, for which VB was good because it was highly approachable sure, but the wheels fell off pretty quick when you had to push it. Delphi was beautiful because it made the easy stuff as easy as VB, but the hard stuff was still easy where in VB it was either impossible or required dropping down to Win32 APIs or calling other languages.

> I will forgive him for Delphi anyway

Sounds like you never used Delphi. Modern C sharp with whatever MS calls the GUI classes this week doesn't hold a candle to how fast you chouls whip up an UI in Delphi and then concentrate on actual functions.

Edit: talking about the Delphi before Microsoft offered Hejlsberg 1 million extra per year and Borland refused to match...

The original C# + Visual Studio was very similar to Delphi in many respects. Whipping up UI in particular; it even used the same anchor system for resizable widgets.
I'm under the impression that it's pretty hard to make custom controls that integrate with the UI builder in dot niet. Back when I used Delphi we had a legion. Made adding new user facing functions (in a data driven app mind) a breeze.
Not at all - I've done that kind of work myself. If anything, the designer integration in VS was more extensive.

(Keep in mind tho that this is about WinForms specifically, and is less true of the frameworks that followed it. It's also about the original .NET Framework, not the present state of affairs with .NET Core, which required a new designer.)

Your take on Delphi is very different to mine!
Why does the intro ad sound like it was recorded in an Elementary School hallway?
The lack of possibility to scroll in time (to skip that part) is what bothered me the most.
(comment deleted)
(comment deleted)
Why is supporting .NET evil?
It locks you in to Microsoft (or it did back then), it's unnecessary. It serves no actual purpose, other than to support that lockin.

A win32 exe will run on Windows 95 through Windows 10, for the most part. You don't need .NET for that to happen.

It's a trap, in other words.

C# is what python programmers wish they had.
As a Python programmer I completely agree. Only last week I started to look into C# and the cross platform capabilities. I have to say it looks really good these days. Wish I learnt C# years ago now...
Microsoft's .NET runtime and C# compiler have been open source and cross platform for a long time now. Even before then, MS published ECMA standards for .NET and C# since the beginning, and the Mono runtime and compiler for C# have been open source and cross platform since the early 2000s:

https://en.wikipedia.org/wiki/Mono_(software)#History

Yes, of course this is true but the tooling ecosystem still hasn't made great strides over to Linux. Rider does exist but is not open source. As far as I know the debugger is not open source as well.
you can do a lot of it with vscode and the CLI. Lots of the other tools probably won't be taken to linux, but mostly you don't really need them, all the non windows specific stuff is pretty well supported across platforms
It's biggest competitor (Java) has exactly the same ownership issues.

C# and .NET Core in particular give me the ability to deploy server or utility software on pretty much anything. You can build a completely self contained executable which is fantastic for deploying software on machines where you can't control the infrastructure.

So, sort of the opposite of lock in.

Java has multiple independent implementations of the JVM spec, so while I don’t think there is any danger in either choice, java is strictly better in this regard.
> It's a trap, in other words.

Usually with a trap, there's typically some sort of follow-through. Let's say I fell for Microsoft's .NET trap 10 years ago. What did Microsoft do, now that they trapped me?

1. Ported .NET to Linux and MacOS with .NET Core. 2. Rebranded the formerly paid-for standard edition of Visual Studio as the Community edition and gave it away for free, even for commercial use so long as your business revenue was below $1mill. 3. Completely open-sourced the .NET Core runtime, base class libraries, JIT and C#/VB.NET compilers. 4. Also open-sourced formerly closed tech like WPF, WinForms, ASP.NET.

Every new project now is just an open source project on github, like github.com/dotnet/machinelearning or example.

In a way, Microsoft still is trying to lock me into continuing to be a .NET developer - by just treating the platform and its developers really well. Gee, such a wild thought to think that maybe companies can change over time?

I'm trapped in the MS ecosystem hacking C# on an M1 Mac and deploying on Debian. Oh wait.
I suggest downloading the file locally first so you can skip over the reverberated bathroom part.
If you want to skip past the intro segments, you can use a different player like the Apple Podcasts site. The actual interview starts at 10:00:

https://podcasts.apple.com/us/podcast/twenty-years-of-c-with...

You can also use their website that's linked to. Just need to hit play & than close the pop up & you should see the player which you can skip around on.

Not the best website user experience but it's a great podcast, even if you're not interested in .NET. Highly recommend all their Geek Out episodes.

I can't believe that I've been hacking C# for 20 years
I know right! And before that I used Delphi from which the vast majority of the language patterns evolved! I still miss some of the wonderful power of Delphi, mainly that it was a decade or more ahead of it's time and had a community culture of quality well architected code with lots of free libraries!

I remember using 'WebBroker' in Delphi 5 and running it on Apache on Windows for a startup back in the day, it really was the best web framework I've ever used and I've used literally a dozen.

I wish he came back to evolving C#. Progress from v1 to v5 was enormous in contrast to v6 to v10.
As a fellow C# aficionado, what do you think it's lacking right now? The main thing I really would like to see is support for discriminated unions - but outside of that, it's pretty awesome.
Nullable references are far from what they could be. When you use them a lot, you often have to override compiler with ! because it is too stupid. I’d also like to have better support for const references as in c++.

I’d like to see more powerful generics, so that something akin to partial template specialisation is possible without going through boxing and unboxing too much.

With regards to safety, well rust has shown what is possible. Async/ await has introduced function colouring which is a kind of bloat.

All in all there are many things that could reduce the bloat, instead of some tiny features that help with value types or to optimise some rare case.

>With regards to safety, well rust has shown what is possible. Async/ await has introduced function colouring which is a kind of bloat.

I do wonder how much effort it'd require

People call async/await function coloring bloat but I think it's exactly where it needs to be. IMO execution yielding needs to be explicit not implicit for use cases where thread identity matters. How are you going to write a UI framework if thread context can change unexpectedly?
Go doesn't need "coloring bloat", but you can still handle the need to run code on a specific thread using LockOSThread.[0] If you absolutely need your code that interacts with the OS GUI functions to be running on the first thread the OS spawned, the documentation even tells you how to do that.

This is an exceptionally low level detail that very few people should ever have to think about. Is there something else you can point to that's pertinent to more developers?

Having written Go for years (as well as a good bit of Rust), I love not having to worry about async/await being the concurrency model, and thread identity is never something I think about. With the upcoming Go release that brings generics, I think there are some code patterns that inherently benefit from an async/await style pattern where you spawn several tasks and await their results, so I'm looking forward to having a generic pattern to spawn goroutines that return a value into a Promise... but the infectious nature of "real" async/await is not something I would consider beneficial in most languages. Rust is a language that is all about low level details and performance, so it actually makes sense there, but that's a rare exception, in my opinion.

Developers generally shouldn't need to worry about things a good runtime can take care of, just like you don't need to worry about when you're going to free an object in C#... the runtime takes care of these details that are rarely relevant to meeting the developer's objectives. When you absolutely need that control, there are usually escape hatches. If the application you're writing frequently requires unlimited control, you should probably be using an unmanaged language like Rust to begin with.

[0]: https://pkg.go.dev/runtime#LockOSThread

>This is an exceptionally low level detail that very few people should ever have to think about.

Every popular UI package uses a UI thread pattern. Every UI dev thinks about this.

This should be handled transparently by the library, not the application developer. Which is not to say that UI frameworks developed decades ago got this right.

As I said, Go handles this problem just fine, apparently disproving your original argument, which was the main point. Async/await with explicit yield points isn’t needed to support this pattern.

As soon as anyone gets it right then perhaps async/await will be the wrong choice but not until then.

My understanding is Go UIs are mostly using html or some other languages/frameworks for UI work. Can you link me to a popular native Golang GUI framework to look at?

There are definitely libraries, such as bindings to GTK: https://github.com/gotk3/gotk3 or Win32: https://github.com/rodrigocfd/windigo

The point remains that it is possible to do these things without async/await, which you seemingly asserted it was not. Everything else is irrelevant, as far as I can tell.

Go absolutely isn’t frequently used to develop native UIs, so of course “popular” is a strange thing to discuss here. Why isn’t it popular? Most likely because the kind of visual UI builder tools used in Visual Studio or Android Studio have never had an equivalent funded for use with Go, due to lack of commercial support for that use case. Beyond that, web UI frameworks are immensely popular these days, and most companies would rather use those, further removing motivation to really “make native GUI happen” in Go, but there are niche use cases out there, as evidenced by the existence of libraries. If Go had come out in the early 2000s, native GUI support would likely have been a much higher priority. This is pretty far off topic, though.

>The point remains that it is possible to do these things without async/await

My point was simply that async/await is good and useful when you're dealing with main threads. You asserted that that was a niche use case and UI programming should not think about thread, to which I replied that it was exceedingly common.

The frameworks you link to do indeed use the main thread pattern which can be blocked by callbacks into Go. You need to be aware of what code you run on the main thread even in the Go code. The problem is naturally colored. Async/await is useful in dealing with that.

I don’t remember claiming the UI thread couldn’t be blocked by callbacks, but it doesn’t seem onerous to avoid, and it is less onerous than having to recolor all of the code in your application and every library you use. You don’t need an async and non-async version of every library in Go, nor do you need to use hacks like “.Result” that every linter likely screams about.

If you use blocking code in C#, even indirectly, you can absolutely block the UI thread, and you may do so accidentally! One async function calls another which calls another which inadvertently does some blocking computation for a few seconds only under certain conditions, freezing the whole UI. If you instead had each UI handler spawn a Goroutine and hand control back to the UI loop immediately, then you would never have any possibility of one of those “async” handlers blocking the UI loop, because every Goroutine is preemptible, and no other Goroutine is running on the locked OS thread.

That robustness alone instantly makes Goroutines better for this. A framework could easily enforce this pattern such that the developer never has to even think about the extra steps of spawning the handler Goroutine or syncing the results back to the UI thread, if there were any demand for native UI frameworks in Go, which there really hasn’t been. I’ve written somewhat similar frameworks in Go for non-GUI purposes.

You don’t seem likely to change your opinion, so I’ll just leave it there, but… the idea that async/await is suboptimal isn’t new. It’s just hard to implement something else, so it has taken languages a long time to do it. Erlang has obviously existed for a long time. Kotlin similarly decided against async/await, and Java is in the process of moving to lightweight tasks like Go using Project Loom. I’m very surprised C# hasn’t announced any intention to move in that direction.

>I don’t remember claiming the UI thread couldn’t be blocked by callbacks

You had claimed that worrying about threading "[...] is an exceptionally low level detail that very few people should ever have to think about." When main threads block, UI devs do need to worry about it. Go's Goroutines do not solve it without thought.

> If you instead had each UI handler spawn a Goroutine

If you did that, you would have a mess of a UI. Like I keep trying to get across, that is not how any of these frameworks work. They are all single threaded and order matters. Implicit execution yielding is insufficient.

Kotlin is probably closer to async/await than Goroutines. Functions are marked with suspend, coloring them like async in C#. Task.Result is replaced with .await() in Kotlin. Scopes are explicitly managed and can be bound to threads, much like the fine grained management you get in C#. If C# added launch { } syntax to fire off async methods, I would not mind it at all, although I don't think it would be significantly different. The coloring remains because it is essential to the problem space.

You keep claiming implicit threading is better for UI but you have no examples. That said, you're free to like or dislike async/await as you choose. I am simply trying to inform you of a use case that you are neglecting.

I claimed that thread identity was an unnecessarily low level concern for most developers to need to worry about, not threading itself. Especially if "most developers" are just running async callbacks in a single threaded event loop, then who cares about thread identity? Not most people, that's for sure. JavaScript certainly never makes you think about thread identity, and it is one of the most popular languages for UI development. You could run a Goroutine-style lightweight threading system inside a single thread too... it's not strictly necessary for it to be as advanced and multi-threaded as Go's default implementation.

> You keep claiming implicit threading is better for UI but you have no examples.

"Implicit threading" isn't causing problems here. When you are running multiple async/await tasks at the same time, you have no guarantee of ordering there either, you just get all the downsides of additional syntactic bloat and the possibility of accidentally blocking the executor because the executor isn't preemptive -- it's cooperative. If someone clicks a button and that spawns an async callback task, and then they click a different button that spawns another async callback task, as long as you don't block the executor with bad code, those tasks will run in any order that they please as time is available on the executor and as "await" calls unblock. If you're somehow only allowing (or only considering) the case where only one UI callback task is allowed to run at a time without making the UI feel locked up, then you have exactly as much control from within a Goroutine over the ordering of what happens as you would in async/await... but since you're (presumably, since you should be) in a separate goroutine from the UI thread, you cannot block the UI thread no matter what you do, even though you can if you are running a single-threaded async/await event loop and do anything wrong. A classic C# approach would be to launch a full OS thread for each callback handler, and then only synchronize with the UI thread to update UI state, and for many use cases that is arguably better than async/await. Full OS threads are typically considered too expensive to launch many of them, which is why async/await came about, but for small numbers of concurrent tasks... async/await doesn't offer much advantage over full threads.

To make this even more explicit, I would argue that a very large percentage of UI developers these days are developing SPAs, and most actual work that a SPA does is performed on the backend, not within the frontend javascript. As UI elements are interacted with, the SPA is firing off asynchronous requests through a load balancer, which does not even guarantee that requests will remotely go to the same backing server. Each server is acting as the callback handler from completely different machines, yet those developers do just fine without thinking about the thread identity of random UI handlers. This is extremely multithreaded and unordered.

The differences between classic async/await and goroutines/lightweight tasks are also a lot fewer than you seem to realize, they aren't radically different, but those differences represent significant improvements. You can emulate classic, cooperative async/await on top of preemptive lightweight threads, but you can't go the other way.

>you have no guarantee of ordering there either,

Here's your fundamental misunderstanding. You absolutely do have control of ordering. Unless you explicitly yield execution, you know you have full control of that thread and will never be pre-empted. That is a useful tool used all over UI dev and game dev.

It's not a misunderstanding on my part, as far as I can tell... if you have any "await" statements in your asynchronous callback, you automatically lose any guarantee of ordering as the runtime yields to other tasks. You do not know whether Task A or Task B will complete first. All you have control over is "critical sections", which will not be interrupted, but also can't contain any I/O (which would yield the executor) or long-running computations (which would lock up the UI) whatsoever. Even the order in which these critical sections are executed across multiple tasks is not guaranteed, so you can't rely on Task A to complete critical section 3 before Task B reaches critical section 2. It isn't ordered!

The only apparent benefit of needing to invoke "await" to break up your implicit critical sections is if you have a crap ton of global state (beyond the UI presentation layer) that you're manipulating without any kind of explicit synchronization at all. That's hardly an inspiring design pattern. You want to talk about messy UIs... that's the kind of mess JavaScript is classically known for, since people could always rely on the single-threaded nature to store everything as a global and manipulate it without a care in the world. Global state should be used sparingly. It's not just an antipattern for maintainability reasons, global state is also generally bad for performance since compiler optimization passes can't be as aggressive around it, and if you ever are running in a multithreaded context, manipulating global state significantly hurts CPU performance as the various cores have to keep synchronizing that global state back and forth.

Perhaps you would like to explain what "control of ordering" means in your context, because not being able to control the order that concurrent tasks complete is a very clear consequence of yielding control to the executor with an "await" statement. If you never yield, then sure, but... that's not very useful in an asynchronous context. You might as well just go back to writing blocking C event loops where every task always runs to completion before any other task can start.

The difference between async/await and pre-emption is that execution will only yield at an await instead of possibly any time. You are guaranteed that two tasks without awaits will not be run concurrently. The magic of async/await is that you can opt out of the yields by avoiding awaits...or yield when you want. You do not need lock style critical sections because every command between yields are essentially a critical section. You can't avoid pre-emption.

And yes, you _can_ guarantee task order A then B by having Task B await A.

However, you're focusing on task order, I'm mostly talking about the order of instructs of a tasks acting like critical sections. Said another way, async/await lets you queue several critical sections in a convenient way. It also gives you the tools to do this on specific scheduling contexts and without thread marshaling or synchronization.

You can bemoan the fact that UI systems are designed around single thread access, that that's messy or whatever but its just the reality. You mention thread synchronization. Exactly so. One of the reasons these frameworks opt for a single UI thread! Every popular UI framework works this way. Dealing with UI threads is inescapable. Running code on a UI thread to access UI state in a serialized way is extremely common and imo async/await handles it well and implicit threading doesn't.

> The difference between async/await and pre-emption is that execution will only yield at an await instead of possibly any time.

Yes, that’s a critical section.

Every thread on your computer is constantly being preempted by the OS thread scheduler. It doesn’t matter to your code, just like it doesn’t matter in a preemptive task system, unless you are manipulating global state without a lock and someone else is trying to manipulate it behind your back. Preemption is a feature, not a bug.

> And yes, you _can_ guarantee task order A then B by having Task B await A.

What…? The whole point is that these are separate tasks started by the GUI framework as asynchronous callbacks. They can’t wait on each other because they don’t know about each other. If you think Go code can’t run its own instructions in a specific order… what?! So of course Go could do that too. But that’s not at all what I’m discussing!

> However, you're focusing on task order, I'm talking about the order of instructs of a tasks acting like critical sections.

I just don’t feel like you know how Go works. The order of instructions is the order you write them, barring any funny compiler optimizations which also apply to C#. It’s not “magically” running all instructions of your function in parallel or something. Whether the task gets interrupted or not is irrelevant — it will resume where it left off. As long as you synchronize access to global state, no one will be observe the interruptions to the task, exactly like how your operating system is interrupting your program constantly and you can’t even tell.

“Implicit threading” isn’t even the right term here, since that implies the compiler or runtime is automatically forking your code into parallel sections for efficiency. Go does not do this. If you write a for loop, it executes every loop iteration sequentially. It doesn’t do wacky things like you seem to believe.

> You can bemoan the fact that UI systems are designed around single thread access, that that's messy or whatever but its just the reality.

That’s not at all what I’m “bemoaning”. If that’s what you’ve gotten from this conversation, then I’m utterly bewildered. I’m done trying to get my points across if communication has broken down to this degree.

Parallel and concurrent are not the same. I'm talking about preventing concurrency of UI tasks. You can guarantee A1-A2-B1-B2 instead of A1-AYield-B1-B2-A2 if you have control of what will yield. With pre-emption, A might unexpectedly yield mid-execution and B could run, even when scheduled to a single thread.

>Whether the task gets interrupted or not is irrelevant — it will resume where it left off. As long as you synchronize access to global state [...]

Cooperative multithreading like async/await is leveraging the fact that actually if you control the interruption, THAT can be your synchronization. You don't need locks. UI programming is usually lock free. A UI thread is more commonly used. Understanding that, you can see that pre-emption _is_ sometimes a bug.

To quote myself:

>>>> If you never yield, then sure, but... that's not very useful in an asynchronous context. You might as well just go back to writing blocking C event loops where every task always runs to completion before any other task can start.

What you're talking about isn't what most people talk about or experience in regards to C# async, and it's still not an actual benefit. In all cases described so far, you're better off explicitly writing A to call B than to try to misuse an async task executor as a weird queue, especially as you said yourself that there is no yielding involved. It's really that simple. What you have described is incredibly brittle code riddled with implicit dependencies, and since you're not yielding, you are locking up the UI.

You still don't get it. Its not that there is no yielding. Its that you as the developer have precise control of when and where to yield.

...But anyway, forget it. I'll be more than happy to use a multi-threaded UI system when such a thing exists.

One of the really interesting things about C# is the breadth of the language, it handles close to if not every possible kind of program that can be written, and that's by design.

In other words it's a fully capable systems level language as well as an application language.

There are a number of design choices that people use to writing high level applications think are funny but when you reflect on them they are there to allow low level control for systems level or high performance programming.

I used to be baffled by some of the designs until I realised that it's a language for everyone/every system, not just for what I personally am doing right now.

I think a more compelling argument would be that C# pioneered a lot of things we take for granted, and it’s impressive how much they’ve been able to change the language over time to keep it relevant as the state of the art advanced, but supporting the legacy of the language requires some tradeoffs.

I agree it is a very capable language, but not every aspect is perfect. I would generally prefer C# over a lot of the alternatives.

C# can call into WinRT async APIs as naturally as ones written in C#. How does Go handle this scenario?
Kind of, only true when speaking about .NET Native, now with standard .NET + CsWinRT it isn't as natural as it used to be.

More an exercise in patience dealing with the WinRT mess.

My discussion was intended to be about two things:

- is function coloring better than alternatives?

- is it possible to interact with APIs that require they be called from a specific UI thread using those alternatives?

The answers are resoundingly “no” and “yes”. The point is that I think C# should at some point adopt lightweight preemptible threads similar to goroutines for the benefits, not that you need to switch to Go for GUI apps on Windows. I don’t agree at all that function coloring for async is a benefit, or that poorly written tasks being able to block the executor is a benefit, which is what the GP basically argued.

C# is a good language, but defending function coloring feels the same as those Go developers who defended not having generics for years. It’s important to be able to recognize the shortcomings in languages so that improvements can be made. Generics will be a benefit to the Go language, even if it took a surprisingly long time to materialize. Async in C# is better than what it had before, but that doesn’t mean it is perfect.

To your question more directly, Go’s runtime automatically uses the underlying native async APIs in most places where it makes sense in the standard library. WinRT probably isn’t used by Go, but I haven’t ever looked. If you’re specifically referring to UI APIs, then this is irrelevant to the discussion, because I’m not saying you should switch to Go for that. Plus, Google doesn’t care about using native windows GUI APIs from Go, so it probably wouldn’t fit your definition of “natural”, since I doubt anyone has put in the effort to clear that bar.

Function coloring is what allows WinRT to be a language-agnostic async API (whether we're talking about UI or not is immaterial - it's all async).

I'm well aware of the shortcomings of coloring, but the point remains that it's the only way to do async that does not result in insular ecosystems with a penchant to rewrite everything so as to gain the benefits of their bespoke green threading (i.e. exactly like Go).

This may change if we can even standardize green threads on OS level, such that all languages can target it in an interoperable way. But the experience with the adoption (or rather lack thereof) of fibers in Win32 shows just how difficult this can be.

I wish for just one thing: native AOP (aspect oriented programming) aka Python-style decorators and context managers. Everything else I need C# already covers adequately.
You mean Java's "Annotation Oriented Programming"?

like @Transactional?

If yes, then I'd really do not want those "magic-like, tricky when it comes to debugging" features

Last time I used Java, it looked to me that AOP is supported with compile-time byte-code rewriting, which might explain why it may feel like magic and be hard to debug. .Net also has a IL-rewriting 3rd party toolkits that enable AOP, namely Postsharp - but my experience with it wasn’t great for similar reasons you stated. Maybe I did something incorrectly, but my impression of tools that generate code at compile time - is that you can’t step through the resulting code. Python on the other hand is both flexible and simple. The decorator code simply wraps the function it decorates (you get a lambda variable that will contain the function you annotate, so you do whatever it is you need to do, and then call the original function. Or not, your choice. ) and doesn’t feel complicated at all. Debugging is simple.

Try it and you might change your mind - perhaps C# designers could use a similar source of inspiration.

> aka Python-style decorators and context managers

Genuinely curious: aren't Python decorators basically the same as C# annotations, and aren't context managers covering the same ground as a C# using statement?

Based on my experience with C# annotations they require cooperation of the function being annotated or the code that calls it. There has to be code somewhere that checks if an annotation of expected type is present, reads its attributes and possibly executes it, and then proceeds based on results.

Python decorators are basically true proxies. The code being decorated or the code that calls it - does not need to be aware in any way of the decorator’s presence, and the decorator gets inserted into the call stack and has access to all call parameters, including the lambda of the decorated function.

Regarding context managers I would need to come back to this post, as I don’t think IDisposable is capable of solving the same use cases, but it’s not on top of my mind as to why.

> Regarding context managers I would need to come back to this post, as I don’t think IDisposable is capable of solving the same use cases, but it’s not on top of my mind as to why.

I'm definitely not as fluent in python as I am in c#, but don't context managers just allow you to define an __enter__ and __exit__ methods that are called when a `with` block begins and ends, respectively? That closely aligns to defining a constructor and .close() method on an IDisposable class, then creating an instance with a `using` statement.

I think Kotlin's type delegation is probably a better way to handle method overrides/wrapping. Fits into the language better.
There is PostSharp for that https://www.postsharp.net/
I mention postsharp in my other reply, but I don’t see it as a compelling substitute - because it’s not a native feature of the language or framework, but also because its compile-time byte-code rewriting nature makes it hard to work with and debug. It fills a niche for sure, but I’d rather it be filled natively by the language itself.
According to the interviews Anders has given that has been by explicit design.

Anders very much creates 'pragmatic industry languages' that optimize for the broader use rather than the individual use. An important part of this is ability for various developers to work on each other's code bases, relatively efficiently.

The contrast to this is a Clojure where you can end up creating basically a DSL that's optimized to the problem space, but that language is then unique to the application and with a higher learning curve when a new developer has to pick it up.

In one of his interviews Anders specifically said he doesn't like AOP because it allows the creation of 'magic' that is hard to understand for other developers. And that's very true at the margins - sure the standard use cases of e.g. logging are not so hard to grok, but it's possibly to weave together incredibly complex/hard to understand/hard to reverse engineer/easy to misunderstand systems when AOP is used heavily or exotically.

So it makes perfect sense when considering he is optimising for an 'industry language' not a 'superstar whose code is hard to average developers to work with' language, that AOP is not in the core language, especially considering extensions such as PostSharp are available.

I’ve heard the same thing mentioned before, but I wonder if the same principles are still held firm or if it’s a new day. I think in the last 20 years C# has accumulated quite a bit of complexity that makes me doubt that the language designers are still firm on this. Take Roslin for an example.

I think that some concerns about possible complexity brought on by AOP are true, but I think they are pretty comparable to what one might be concerned about as it relates to Roslin.

But I am not trying to convince anyone really, I am just answering the posters question: what do I wish was added to C#.

For anyone not familiar, Roslyn is the language server for compilation, analysis and refactoring of code.

I don't understand the premise of your post but I would like to.

How does having these advanced features at the code compilation stage which is used by compiler and visual studio plugin programmers, have any affect on the complexity of application developers? The application developer doesn't have to think about Roslyn/language server at all, they just consume it's features. The application developer is concerned with the complexity/magic in the code, not how the code was compiled?

Perhaps I am not referring to the correct technology/feature if you find it so confusing? In my post all I was trying to say is that enabling writing code that generates predicates, expressions etc, literally syntactical building blocks of more code - via code in an inception-like manner - seems to be at least equally complex an endeavor as somewhat arguably non-trivial matter of creating or using python-like AOP decorator “proxies”. So presence of Roslyn kind of disproves that the emphasis on simplicity is still in effect, at least in my mind. Hope that clears up my point.
I've just happened to stumble on this discussion on a C# 11 feature, where someone who I think is on the language team (HaloFour)says ".. using source generators for AOP has been effectively rejected by the language team ..".

https://github.com/dotnet/csharplang/discussions/5735#discus...

I don’t think this is a new revelation, but rather someone re-stating what was already stated in the original doc introducing source generators that I’ve read a few month ago, around November 2021-ish? … which said something along the lines that they explicitly making it impossible to use it for AOP.

Personally I think that dogmatic crusade against AOP while adding a menagerie of syntax sugar hieroglyphics :? :: !! :+ … is a bit misguided, but whatever.

they are working on DUs, but I think it is a bit stalled as it gets a bit odd with their enum syntax, but you can actually do them reasonably simply right now. I have a stack overflow answer here (and a link to a F# example I rewrote using C#) https://stackoverflow.com/questions/63724308/using-c-sharp-9...

The only thing is you don't get exhaustive type checking, now depending on how passionate you are, that can be a must have, but I actually don't mind without.

One of the big lacking things for me is the lack of being able to make types from basic types, sort of like a typedef in C++. F# allows you to do it, but it actually fakes it as .NET itself has no concept of this. This is a PITA if you want types for things like CustomerId which is just an int, but you want it such that CustomerId is typed and can't be passed to other places that would take an int, only if places that take that type. You can model it with a record, but it becomes a bit unnatural and makes mapping it to datastores more effort.

There's a few more things I think they could steal from F#

- a way to do pipe operators - Type providers like https://fsprojects.github.io/FSharp.Data/ - computation expressions (monads without the drama)

I do think the pipe operator is very tied into the first-class currying support and HM type inference in F#. In C# I think it makes a lot less sense.

To be honest I think each language should play to their strengths and only "steal" things that benefit their current market of users and how they work. If it augments them that's fine. Otherwise you risk cluttering the language with too many ways to do things. If you want all these F# features you probably just want F# - its not too hard to call `dotnet new classlib -lang F#` these days and mix both in together in a solution.

Having worked in a few C++ shops, at least from my experience, it can result in a lot of useless debates and internal wars if you keep on adding different features from different paradigms into the language whilst trying to keep backwards compatibility.

My view is that if you want unions/non-nullability/data vs classes even if C# gives you the features, it feels more natural to code that way in F# due to the environment around the features and years of optimising that code path. It's just less noisy and less workaround'y. i.e. each language typically prioritizes enhancements and features based on what users in that language are having pain points on. In that sense F# is much further on the data/function paradigm than C# is - conversely when doing low level stuff C# is often in front having to cater for that for some time.

The pipe operator can be replaced by a T.Pipe(Func<T, U>) generic extension function.

In fact, I've defined it and occasionally use it in F# when I'm working with extensive C# fluent APIs, because it looks nicer than breaking them up with a single |>.

I've defined it myself (when doing C# with functional heavy code but it feels clunky) but there's still edge cases where it isn't as nice especially with functions of multiple args, or functions passed in. Sure C# can do these things with more code, just like F# can jump into some unsafe code, but its nicer in F# IMO. The other thing is that the operator as above is probably allocation heavy especially when needing to complete a function inside the lambda whereas the pipe in F# the compiler just rewrites the code. In hot loops or highly repeated workflows this makes a difference.

C# Fluent API's aren't really piped things since they usually just mutate a variable underneath (hence the ignore often at the end of them in F#). I don't think fluent API's and piped functions are the same thing, and the pipe operator allows any function that matches the signature to be put in.

We have extension methods; I would like extension properties.
Not C# itself, but I'd love to see XNA come back, or Monogame get support from Microsoft, or something.
Monogame has some support from Microsoft,

https://dotnet.microsoft.com/en-us/apps/games/ecosystem

But the problem is the WinDev silo, they are pretty much against anything not C++/COM.

XNA died because when Phil Spencer left, no one else cared and naturally XNA got rewritten in C++ as DirectXTK.

More about .Net than C# but a consistent story on UI code, something I could expect to rely on for the next ten or twenty years.
>Progress from v1 to v5 was enormous in contrast to v6 to v10.

That just indicates they did the most important and useful stuff first.

I still remember how going from 1.1 to 2.0 was such a massive improvement.

At some point making it more versatile actually makes it a lot harder to newcomers to learn, especially because all the old articles, code snippets, stack overflow posts etc. stay there and tell you the old (and sometimes now wrong) way to do things.

I thought it was interesting when Anders mentioned that 30% of C# developers use Unity. Unity doesn't use Microsoft's CoreCLR runtime, but instead uses the Mono runtime or an AOT compiler Unity created called IL2CPP, which converts IL code generated by the C# compiler to C++ that gets compiled to native code. One of the reasons for this is that CoreCLR supports Windows, macOS and Linux, whereas Mono and IL2CPP together support every platform, including consoles. Anyway, it's interesting to me that a significant number of developers are using C# but are using a runtime other than Microsoft's CoreCLR runtime.
It's worth mentioning that there's now a third way C# is utilised in Unity which is via their new 'Burst' compiler, part of their wider move to an Entity Component System they've called DOTS[1]. It supports a subset of C# that gets compiled into memory safe jobs that can run safely on and spread across different threads. I'm using the Burst compiler + jobs in my full-time game project and the performance is seriously good for whenever you just need to process a lot of data. You can use Burst with both Mono and IL2CPP script modes.

The go-to deep dive on how DOTS/Burst works is pretty much everything Jackson Dunstan has ever written on the matter [2] but specifically the article that digs into how IL2CPP calls Burst is interesting.[3]

[1] - https://docs.unity3d.com/Packages/com.unity.entities@0.17/ma...

[2] - https://www.jacksondunstan.com/?s=burst

[3] - https://www.jacksondunstan.com/articles/5251

I have to say, I wonder if this could be augmented into more general applications that need concurrency or parallelism, since this sounds like an optimal way to make realtime or semi real time systems, at least on the surface
It's also a bit of a curious coincidence that it's a company founded in Denmark, Unity, transpiling a language invented by a Dane, C#, into another language invented by a Dane, C++.
Also, Anders is Dane and Mads Torgersen (current C# lead designer) is Dane
I vividly remember when I started using C#, I can say that 20 years later I only feel a little bit bad about leaving C++ (mostly) behind and glad I didn't have to go the Java route.
I just wish more company adopt .net instead of java, the new .net core thing is a game changer. C# is a good language, combined with the runtime that can run anywhere? it's even better. The thing is enterprise companies mostly went with the safe route which is java, or most that use .net are stuck in the old version.

Hopefully it will change for c#, MS needs something for startups / enterprise / new dev to excite them to choose .net core! (despite all the bad things in the past)

The new .net has been amazing for us. Biggest QoL improvement would be the self contained deployments. We do B2B software, and being able to email a customer a zip that is guaranteed to work on any windows server without bullshit has been beyond incredible.

Our customers love the experience too. We switched to SQLite for persistence, so there are literally zero prereqs to an install now.

One of the reasons I love Go. Does the .zip file also contain the runtime? E.g., nothing is required to be installed on the remote system?
Yes. .net now has self contained builds. The whole deploy can be a single executable with some caveats.
My biggest hold-out for using C# at a medium-sized company would still be how awful Projects and Solutions are, when working with a big team.

I'd have to develop my own tooling, first.

I don't see how that could possibly be a problem.

Yes, the old (pre-.NET Core), .csproj files were a mess, but the post-2017 .csproj/.props/.targets files are a pleasure to work with.

Well, that's good to hear. We had problems where you would add some dependencies, and I would add the same dependencies, but the GUIDs would be different, and then everything was impossible to merge. You're saying that's gotten better?
Sounds like you were doing something wrong to manage your solution files.
To be fair every now and then the project files would get messed up by the tooling, and happened more often with nuget. It's still rare though and can be fixed by digging in to what's going on, or just recreating the projects and readding the source files.
Our repo has nearly 15k .cs files across 700 .csproj files. If we can do this successfully, I think anyone can.
With .net core references are much cleaner. There isn't a reference to every file it's handled automatically. The whole csproj hell you're talking about is gone now.
> I just wish more company adopt .net instead of java, the new .net core thing is a game changer. C# is a good language, combined with the runtime that can run anywhere? it's even better.

Yeah. Much better. Until you try to run it from a network share. Or your quad core laptop "Intel inside " goes in thermal throttling because .NET is "optimizing".

I'm curious about what is going on while .NET is "optimizing"?

I haven't seen something like that, my laptop is AMD though? (I write .net code) Unsure how much of a difference that makes.

I like .Net, it's not without it's issues, I think the same is true with Java, having been the administrator for some Java servers in the past, I've seen plenty of Java issues that seemed crazy ~ but I think we're talking about how great things are now, maybe, while saying it doesn't matter because how bad the legacy tech is?

It's compiling interpreted code into machine code that's specific to the processor it's running on.

Without this step it can still run but being interpreted it runs slower.

Working on a polyglot consulting company, one reason that doesn't happen is that .NET hasn't fully embraced cross platform like Java.

Outside Windows you only get a subset of the ecosystem, so you always have to watch out for libraries that have been written or ported to .NET Core explicitly, as a large majority of them might work on .NET Core on Windows, but fail everywhere else due to what they are using from Windows/COM.

Additionally there are 15 years of .NET Framework ecosystem that can't be easily migrated to .NET Core due to breaking changes.

Java doesn't suffer from this problem.

Is your knowledge out of date? .NET Core is no more, we're on .NET 6 now.

What libraries are you referring to, because outside of niche things like WCF there's very little left that doesn't run on every OS .NET 6 runs on now. Of course COM won't run on Linux.

All the Win32 component libraries, WPF, Entity Framework + extensions. There's oodles of these packages on nuget
.NET 6 is .NET Core renamed.

Niche stuff like enterprise in-house build built 2002 and 2015 when Windows was the only game in town (mono wasn't really taken seriously), enterprise CMS like Sitecore and SharePoint, GUI frameworks (component libraries from DevExpress, ComponentOne, Telerik among others).

And all the stuff Visual Studio Professional/Ultimate have on the box and Rider will never support, nor VSMac/VSCode.

I moved from C# to Java, largely because its more widely used and has more libraries out there. I miss C# though, Java has so many silly wrinkles. Java is getting better with records and ZGC and whatever they can copy from Kotlin. The worst part of Java is Spring and super elitist programmers that seem to look down on everyone else.
.NET also has plenty of silly wrinkles, and C#11 is getting a couple of new ones, see twitter storm regarding !!.
I don’t know where this “copy from kotlin” meme came from. Why would an elephant copy something completely non-novel from a mouse? Records, pattern matching and the general direction java is going in are standard ML features, dating decades back. But even then, Scala has been doing nigh everything kotlin did much earlier.
My uncle was a developer, and every time he got a new Turbo Pascal / Borland Pascal / Delphi compiler, he would give me his old one to learn from. Totally got me started in my career.
Learned C# in school, I still use it today if I have to hack together a quick app or something on Windows for myself.

Wish it was more used around.