Hacker News new | past | comments | ask | show | jobs | submit login
Official proposal for Type Unions in C# (github.com/dotnet)
308 points by Fervicus 33 days ago | hide | past | favorite | 302 comments



Huh, I did F# for years with discriminated unions, and I guess I just assumed C# would have had them by now.

I know not everyone likes them, but for typed languages I find it extremely hard to go back to languages without ADTs of some kind. I do Java for my current job, and Java is generally fine enough, but it's a little annoying when I have to do whacky workarounds with wrapper classes to get something that would be done in three lines of F#.


F# is so hard to walk back from. I wish Microsoft would support it better and actually push it, because it's such a perfect sweet spot. Most of the functional advantages without being shackled to pure functions and the like is so easy to develop in.

Instead they've been, very slowly, turning C# into F#, which is even weirder to watch.


I agree; F# is probably my favorite of the "compromise" functional languages [1], where you can drop into the "wrong" way when necessary. F# pushes a more or less pure approach, but in some cases, like when mutation will be a bit easier and/or faster, it's easy to do that as well. I also think that even F#'s OOP is actually really pleasant...If nothing else, it's a lot more terse than C#'s.

I miss writing it; I haven't had a job using it in a few years but I really enjoyed writing code in it when I did. Most of my personal projects haven't really been able to use .NET for awhile, so I never seem to have an excuse to play with F# in my personal time.

I never got into C#, so I can't say that I really feel the pain of the C# transition into F#.

[1] At least in the typed world. I'm also really partial to Clojure.


I'm glad they're simply turning C# into F#. They're coming to us where we are, rather than forcing us to make a jump. I'm a relatively "blue collar" developer, I know a little about FP (not enough to be productive in F#) but I have tons of C# experience. I appreciate them taking the good stuff from F# and making it understandable to C# developers. It's been a delightful progression; no big jumps, no paradigm shifts, just the steady addition of useful new features that I can understand.


I agree with you, as far as adding new features to C# from F#. When I have the time to refactor larger codebases, I'm often finding new C# features/syntax makes the code easier to understand and reason about (often due to decreasing the number of lines needed to accomplish the same thing, without overcomplicating things). I do make sure that if I'm introducing new language features, that I'm refactoring related portions of code, so I'm not leaving behind the "old" way of doing things while also introducing the "new" way for the same functionality. I'm always thinking of the next developer with anger issues coming into the codebase after me.


> I'm glad they're simply turning C# into F#.

I'm not. I find F#s syntax much more pleasing and elegant, and I'm afraid F# will wither away when C# is a watered down version of it.


So the issue for those of us in F# land, is that it's really not a big jump from C#. I've taught it (lightly) to at least one C# dev who basically picked it up in a weekend.

Something that helps, a ton, is as you're transitioning, you can write code almost identically to how you did before. It doesn't force FP on you at all. It's less useful if you try to do that, because you're not taking advantage of the language, but it makes testing your beliefs on how things work SUPER easy because you can set it up in the OOP way, get the result you expect, then set up in what you think is the correct FP way, and see if it matches.

The only issue is there's a severe lack of beginner friendly documentation (although it's leagues better than it used to be). I honestly think that if you're a C# dev, it's probably worth giving it a weekend or two to play with. ESPECIALLY if you like linq syntax.


I feel the same about Scala. I use Scala3 daily and almost every other language is such a step back. I have looked at F# and it looks like one of the only other languages I would enjoy as much as Scala. Haskell is nice too, but the ecosystem is just not quite there. F# being able to tap into the rich C# ecosystem and Scala being able to tap into the Java ecosystem is such a win and makes them feel a lot less niche when you use them.


I do like Scala, and I haven't touched Scala3, but I've found it pretty unwieldy for actual use. It feels like I'm constantly fighting with the type system in a way that ends up being annoying an unpleasant.

To be clear, it's not like I'm new to functional languages and their type systems, I've been doing Haskell for forever, and I did F# for a multiple years, but Scala has never really clicked for me.

Personally, for doing JVM stuff, I'm a pretty die-hard Clojure advocate. I feel like it's easy for me to write Clojure in the way that I think, while still allowing me to mooch off the entire Java ecosystem, and if there are things that I don't like about the language it's really not that hard to macro in a new feature (though obviously this should be used sparingly).

I definitely recommend giving F# a try. I think it's an extremely underrated language.


My experience is diagonally opposite. I haven’t written Scala for a while now, I guess a year or two by now, and just a few days ago got back to it because the logic I wanted to express was hard to fit in to Java, Kotlin, C#, Go. I have certain performance and correctness requirements and most stuff is JVM based. So I thought like okay, I can isolate it in a service and invoke from another JVM, but let me just try to express my type families and the algos the easy way around, and whoosh I see Scala compile and give me the correct results on my first attempt.

That was a bit of a surprise - I’d forgotten how expressive Scala is. I didn’t need any “advanced” type system features. But my time to market was literally a couple hours instead of days like with C#, Java, Kotlin, Go, bending to fit the logic into the confines of their type systems. Kotlin is just awesome and very expressive. But it turns out, to me, Scala is somehow very natural - just write what you think, and it works.

Interestingly, that was also my experience many years ago when I first exposed myself to Scala in a numerical analysis class just to avoid Java, and it just worked right away. I recall well that surprised feeling. So I’m kinda happy to move back to Scala now. But I’ll also try to implement the same in F#, right after delivering the next feature set.


> I definitely recommend giving F# a try. I think it's an extremely underrated language.

Do you have any recommendations for people who do not have C#/.NET experience who want to rip their toes in F#? The last time I tried the language I bumped my head on the .NET parts. :/


Out of curiosity, what confused you about the .NET stuff? I didn't have any .NET experience going into F# either [1] and I didn't find it too hard to pick up the .NET stuff.

For the most part I didn't need most of the built in .NET libraries; the F# stuff was fine, but the ones I ended up using a lot were the threadsafe libraries for when I needed mutation across threads. ConcurrentDictionary was one I used an awful lot, ConcurrentBag occasionally, SemaphorSlim, and Interlocked Incrementors.

The reason that the .NET compatibility was handy to me was because it made it easy to get third party libraries without much fuss, which was great because they were generally pretty well supported.

The rule of thumb that I used was that F#, while it kind of looks like Haskell, is not Haskell, and specifically it's not lazy, and as such I always kind of pretended I was in the `do` block. Things execute top down, and you can mix in side effects wherever, so you kind of have to pretend everything is in an IO monad, or at least if you see anything that has a side effect in there.

[1] I was hired at Jet.com specifically because I had Haskell and Erlang experience prior. I had never written any significant C# code before.


I was working on a Linux laptop and the installation process wasn't immediately clear. Some of the frameworks I looked at didn't target the most recent .NET version so I had to install multiple versions. Once I had it installed, I had compilation failures with a project that was built on Windows. The team that ran it wasn't sure what was happening.

Also, having the documentation split between the F# parts on one site and the .NET parts on another site was a bit of a pain.

It might have gone better if I was starting with a book instead of picking docs off the web.


Hopefully this website can get you further with the installation. There will probably still be issues between Windows and Linux specific tutorials, depending on what you are trying to do.

https://fsharp.org/use/linux/


Ah, yeah, the non-windows stuff was pretty clunky for awhile, but I do feel like it's gotten a bit better at the command line support.

FWIW I think it works fine with Nix, so if you're not opposed to using that package manager it should pretty trivial to get different versions working with a nix shell and/or flake.


It is a dream learning guest languages, while trying to avoid the platform, one really needs to understand UNIX, Web, .NET, Java, Erlang... for using whatever alternative leaky abstractions are provided on top of them.

In F#'s case, there are plenty of good books, and then it is really needed to learn the platform.

Some well known ones => Stylish F#, F# in Action, Domain Modeling Made Functional


Thank you for the recommendations. I still find using a physical book easier to learn from, even if they do tend to be out of date faster than online resources. I feel as though F# doesn't move as fast as other parts of the .NET ecosystem, and in particular the Domain Modeling Made Functional seems like it will be relevant for years to come.


It's not so much that I don't want to learn the platform. It's that there is often an inherent sequencing. The expectation is you already know the platform and now you are learning the guest language. I just want to be taught the guest language and the platform at the same time.


While a bit dated, F# For Fun and Profit is a great learning resource:

https://fsharpforfunandprofit.com/


When I have to use Scala, I am always confused by the amount of magic that is happening behind the scenes. It's so much magic that even the best IDEs become confused with me. C# is smarter in that regard: 99% of useful syntax is method calls, and typing the dot doesn't drown you in overly generic suggestions.

From what I've seen, Scala3 is better in this regard (at least the magic part), but I since Spark doesn't support it yet, my exposure to it has been limited.


There is enough magic with attributes, code generators, dynamic, reflection and expression trees.

Thing is the community doesn't reach that often to them.


It makes sense from a language adoption standpoint, especially if you look at typescript for example, which is also by Microsoft. Though I agree with you personally


Looking at the syntax in the type union proposal, I hope they take a bit more from F#. It really needs some work.


What are some issues you see with the current proposal?


Good news is that you can achieve this with relatively little boilerplate in Java with sealed interfaces and records now. Relative to Java that is


Java can't do the ad-hoc mixing of types from different libraries, which is something I've wanted/needed for some ORM+expressions work I was doing. I really like where C# is going with this. Hopefully it will accelerate continuation of the evolution of Java.

I'm almost of the mind to use F# or C# instead of waiting.


Those are untagged union types and are less useful than you think.

For one, they don't work for generic code where you want to discriminate. I.e., if you have a generic A or B type, you can't pattern match on it. And it's hard to add restrictions to the generic types such that it would work.

Such types sort of work in TypeScript, but TypeScript also has structural typing, meaning that it's designed for it.


Yeah, I know, and I have started using those, though that doesn't undo the last 15 years of Java code that I've written where that wasn't really an option.

Still, good to see Java joining the 21st century at least.


> that doesn't undo the last 15 years of Java code that I've written where that wasn't really an option.

Same for C# with this new feature. Sum types (as I like to call tagged/type unions) are such an important tool to have, not having it makes languages resort to all kinds of abominations, e.g. exceptions, product types (= records, structs, etc) that are actually sum types, "null to signify something went wrong", etc.

Not having them from a language's start results in an std lib that does not use them and hence teaches new comer to the language a bad way to do it.


As of 2020 (when I stopped using F# and .Net in general), the CLR only had inheritance and F#'s enums were actually implemented using inheritance. Eg Some and None were derived classes of Option class. You would see it if you inspected an F# assembly in a decompiler. I don't know if it's still the same today. This C# proposal has anonymous enums using `A or B` syntax which would be hard to make work as sugar over inheritance, so I guess the CLR would support enums first-class for this to work (if it doesn't already).


This is only partially true. Both Some and None were always instances of single FSharpOption class (None is actually not an instance, but null), however custom DUs are indeed compiled into classes with inheritance


Ah, then I misremembered.


what's is your .NET replacement ?


FYI, Java has tagged union types (since version 16).

Pattern matching too.


> Java is generally fine enough, but it's a little annoying when I have to do whacky workarounds with wrapper classes to get something that would be done in three lines

This is a weird take, given that Java has had ADTs for years

Records https://en.wikipedia.org/wiki/Java_version_history#Java_16

Sealed classes https://en.wikipedia.org/wiki/Java_version_history#Java_17

And pattern matching for almost a year https://en.wikipedia.org/wiki/Java_version_history#Java_21

Otherwise big agree on how delightful to work with F# is.


Most of my jobs until recently have been using Java 11, so I didn’t get to use these things.


I see. But no hope is lost! You can always lean on Scala. It runs fine on any Java >= 8 and it's great. Unless you intentionally used is badly (but you could say that about any language).


Hmm. You find it hard to go back to languages without ADTs yet you use Java for your day job. Okay.


Really excited for this proposal as it's been the main thing I have to apoligize for when extolling the values of C#. Outside of this it's hard to think of other major features C# lacks for a language.

Additionally, excited to now watch this go in and people on HN still act like C# is the same it was 10 years ago.


Proper type aliases would be nice, but I have to say I really enjoy coding C# these days.


Alias-ing any type was added in C# 12 - do you mean something more than this? https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...


In this case aliasing refers to memory aliasing. As in, given a common struct that represents a type union, you can overlap the fields that satisfy 'unmanaged' constraint (think int, MyStruct(short, long), Guid) between each other, but you cannot overlap them with fields that are or contain object references, because this violates the spec and GC will likely crash upon encountering data that turns into garbage when interpreted as a pointer.


An official proposal was just submitted for type unions:

https://github.com/dotnet/csharplang/blob/18a527bcc1f0bdaf54...


Why hello again :) I really appreciate your projects and noticed the web server one. There's an ask - please prefer built-in containers unless you have to use custom ones, at least for now. It is also not necessary to define aliases for types accessible directly: `using Namespace.AnotherNamespace` is enough to access them. There is no need to re-define the alias for `AnotherNamespace` if it matches either. When you have time, please look at sharpl PR which simplifies the implementation and makes it more compiler-friendly: https://github.com/codr7/sharpl/pull/2

In any case, welcome to C# and thanks!


Hello there :)

Just a little bit of patience with the collections, I'm slowly learning to trust the native offerings, at least there's a regular List at the bottom now, much thanks to you.

I'm just used to using more descriptive type names, that's all. It bothers me to no end when I have to keep guessing and memorizing the correct primitive type for a concept. Also makes the code more difficult to read.

I'll have a look at the PR, thank you!


> it's hard to think of other major features C# lacks for a language

Heh yeah well, for better or worse, it seems to be C#’s design goal to have every single feature possible, with multiple variations of each of them. It means it’s able to cater to everyone and doesn’t turn people off the way strongly opinionated languages can. But it can make it a bit tricky to pick up.


> Every single feature possible, with multiple variations of each of them

I'd really push back against this - where do you see API duplication? IMO the C# team is _incredibly_ conservative with new language features and only add in things that fit a missing part of the language, and when doing so try to also weave in existing C# features to build upon.

I think if anything you may be referring to syntax options? There are a few syntactical ways to do the same things for sure, but I don't think that's bad, as each has their own use. But for bigger tentpole features like records or Span<T> or whatever, they are directly addressing a need and aren't just iterative versions of other things.


Can anyone explain why this is called "type unions"? I've never heard it named that before. It's a bit weird, because it's not a union across types (like ALGOL68), it appears to be a tagged union like in ML-family languages.

Is this just a case of "C# developers like to make up different names than established terminology"? (see: SelectMany, IEnumerable, etc.)


"Tagged" sounds like an implementation detail to me (that it has a "tag" internally to tell between types). I suspect they added "type" to "union" to make it clear what is about (about types). The syntax itself has just "union", judging by the document.

UPD. They have this in the FAQ:

  Q: Why are there no tagged unions?
  A: Union structs are both tagged unions and type unions. Under the hood, a union struct is a tagged union, even exposing an enum property that is the tag to enable faster compiler generated code, but in the language it is presented as a type union to allow you to interact with it in familiar ways, like type tests, casts and pattern matching.


I don't think tag is an implementation detail generally, for example:

    enum Option<T> {
        None,
        Some(T),
    }
The tags are "None" and "Some" which are definitely user facing.

But I see what they mean a bit with the C# example:

    union U 
    {
        A(int x, string y);
        B(int z);
        C;
    }
So it seems like "A", "B", and "C" are tags but also their own distinct types, with a implicit conversions between those and the overall union type


Tagged unions are an implementation detail and are often not the optimal way to solve this problem.

Take Option<Infallible>, it's a ZST, not only is there no "tag" there isn't any data at all. In type theory this is fine, we added an empty type to the unit type, we got a unit type.


Because the proposal is about several kinds of unions:

- closed hierarchies of reference types that the compiler will verify at build time

- value types that use compiler tricks to behave like closed hierarchies (tagged unions)

- custom types that can have any implementation but can hook into the same compiler mechanisms to behave like a union of types

- ad-hoc unions of existing types


I’ve lost track of all of the red/blue/white/black pill color metaphors, but unions with exhaustive pattern matching is one of the toughest of all programming language features to live without once you become aware of it.

I’ve never felt like I’ve fully understood the implications of the expression problem https://en.wikipedia.org/wiki/Expression_problem but my best/latest personal hypothesis is that providing extension points via conventional polymorphism might be best suited for unknown/future clients who might extend the code, but unions with exhaustive pattern matching seem better suited for code that I or my team owns. I don’t typically want to extend that code. More often, I instead want to update my core data structures to reflect ongoing changes in my understanding of the business domain, more often than not using these Union-type relationships, and then lean on the compiler errors maximally to get feedback about where the rest of the imperative code now no longer matches the business domain data structures.


I wrote this to help me understand the differences.

https://deliberate-software.com/christmas-f-number-polymorph...


Maybe I'm off, but to me the gist of the expression problem can be explained by contrasting how code extensibility is achieved in OOP/FP.

OOP Approach with interface/inheritance:

Easy: Adding new types (variants) of a base class/interface. Hard: Adding new functionality to the base class/interface, as it requires implementing it in all existing types.

FP Approach with Discriminated Unions:

Easy: Adding new functions. Create a function and match on the DU; the compiler ensures all cases are handled. Hard: Adding new types to the DU, as it requires updating all existing exhaustive pattern matches throughout the codebase.

Here's some Kotlin code. Kotlin is great because it can do both really well.

  // Object-Oriented Approach
  interface Shape {
      fun area(): Double
      fun perimeter(): Double
  }

  class Circle(val radius: Double) : Shape {
      override fun area() = Math.PI \* radius \* radius
      override fun perimeter() = 2 \* Math.PI \* radius
  }

  class Rectangle(val width: Double, val height: Double) : Shape {
      override fun area() = width \* height
      override fun perimeter() = 2 \* (width + height)
  }

  // Easy to add new shape
  class Triangle(val a: Double, val b: Double, val c: Double) : Shape {
      override fun area(): Double {
          val s = (a + b + c) / 2
          return Math.sqrt(s \* (s - a) \* (s - b) \* (s - c))
      }
      override fun perimeter() = a + b + c
  }

  // Hard to add new function (need to modify all existing shapes)
  // interface Shape {
  //     fun area(): Double
  //     fun perimeter(): Double
  //     fun draw(): String  // New function
  // }

  // Functional Approach
  sealed class ShapeFP {
      data class CircleFP(val radius: Double) : ShapeFP()
      data class RectangleFP(val width: Double, val height: Double) : ShapeFP()
  }

  fun area(shape: ShapeFP): Double = when (shape) {
      is ShapeFP.CircleFP -> Math.PI \* shape.radius \* shape.radius
      is ShapeFP.RectangleFP -> shape.width \* shape.height
  }

  fun perimeter(shape: ShapeFP): Double = when (shape) {
      is ShapeFP.CircleFP -> 2 \* Math.PI \* shape.radius
      is ShapeFP.RectangleFP -> 2 \* (shape.width + shape.height)
  }

  // Easy to add new function
  fun draw(shape: ShapeFP): String = when (shape) {
      is ShapeFP.CircleFP -> "O"
      is ShapeFP.RectangleFP -> "[]"
  }

  // Hard to add new shape (need to update all existing functions)
  // sealed class ShapeFP {
  //     data class CircleFP(val radius: Double) : ShapeFP()
  //     data class RectangleFP(val width: Double, val height: Double) : ShapeFP()
  //     data class TriangleFP(val a: Double, val b: Double, val c: Double) : ShapeFP()
  // }


When statement will be exhaustive thanks to the sealed class, so compiler catches this, one of the great things about Kotlin.


I think you've nailed the definitions.

However the definitions make the 2 choices (adding new types vs adding new functions/operations) sound like a toss-up.

It is the fact that I tend to find the FP/DU approach so much more frequently useful for my/my team's own code that makes me wonder if I'm missing something.

Perhaps the important distinction I've been missing is in Wikipedia's definition:

"The goal is to define a data abstraction that is extensible both in its representations and its behaviors, where one can add new representations and new behaviors to the data abstraction, without recompiling existing code, and while retaining static type safety (e.g., no casts)."

... but when I'm working on my own/team's code, it is perfectly sensible to recompile the code constantly.


The reason why it matters less than people intuitively think is precisely that it matters a lot less when you're in full control of both the operations and the types anyhow, and that's actually the most common case. Generally you are "composing" in library code, that is, just using it, not extending the library itself.

When you are extending, you actually want to choose the correct thing depending on what you need. Going the wrong direction is painful in both directions.

Personally I think one of the reasons sum types are greeted with such "oh my gosh where have you been all my life" reactions is precisely that we had type extension as our only option for so long. If we had had only sum types, and type extension was given to us for the first time in obscure languages 20 years ago and they only really started getting popular in the last 5 or so, I think they'd be considered in much the same way. Just as in a world with only screwdrivers, the invention of the hammer would be hailed as a revolution... and in a world with only hammers, the invention of the screwdriver would be hailed as a revolution. But in both cases the real mystery is how the hypothetical world got that far in the first place.

Not that they aren't useful; consider what it means that I'm analogizing them to something like a hammer and a screwdriver, not an oil filter remover or something. It is weird that we were missing one of them for as long as we were in the mainstream.


Sum types are not a new discovery for me, personally.


And I've known about them for, let's see, at least fifteen years, and I've definitely gotten over my "oh my gosh I must use these for everything" phase.

Though I do wonder as well how many people encountered them in their "spread their wings" phase and happened to be learning about sum types just as their general programming skill was leveling up in general, and conflate the two. When you learn how to use both skillfully, I really feel like the differences collapse quite a bit. I see so, so much bad code with type-based extension, but it's not because they're using type-based extension, but just that it's bad code, regardless. Of course bad type-extension code is worse than good sum types code, but there's still times and places for both approaches when you know what you're doing with both.


What do you do when you are handed a DLL with publicly exposed types? You can't recompile someone else's DLL without the source, but the public non-sealed types are totally open to inherit from; it's just a question of how useful the inheritance would actually be if there's not a logical public interface (as provided by the original designer).

Maybe you can read all the public fields, but you can't actually modify them or create functions that modify the object. You then must wrapper around the instances and fight to bridge every little behavior between their code and yours.

This to me is evidence that one core tenet of OOP "Open to extension" is in-practice meaningless.


Is the terminology slightly off? AIUI TypeScript has type unions.

But this looks like a discriminated union which I'd recognise from F# or Haskell. The distinction I'd draw is that for a DU there are named case constructors. Yes?


TypeScript has "union types". This proposal refers to them as "ad hoc unions". I don't think "type union" is a term of the art - at least I haven't heard it before. It seems to be something the C# people are making up to describe sum types.


> I don't think "type union" is a term of the art

"sum type" is the term of art in Computer Science theory, but "union type" is also used.

See

https://en.wikipedia.org/wiki/Type_theory#Sum_type

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


At this point, calling it by its correct name (Sum Types) would leave them looking stupid for not using the correct name (Product Types) for their “records”


> not using the correct name (Product Types) for their “records”

There is no single "correct name" as there is no single "problem domain" that covers the algebra of type theory, and the pragmatics of programming in engineering, along with other areas.


That construct has been called a “record” since, I believe, 1970 in Pascal.


But then what name would they use for tuples, which are distinct from records?


both tuples and record (structs) are product types. tuples are "untagged", records/structs are "tagged" (the members have names).

sum types also have tagged and untagged variants. the untagged variants are less useful (more cumbersome) in case of sum types and hence often are not implemented in languages.


C# tuple field names are an interesting case, spiritually similar to this proposal's ad hoc unions: persisted in compiled output via attributes, respected by the compiler even across assembly boundaries, but not actually part of the resulting CLR type. So, e.g., if

  var p = (x: 1, y: 2);
  var q = (u: 1, y: 2);
  var f = ((int x, int y) a) => a.x + a.y;
then

  p == q 
  f(p) == 3
  f(q) == 3
  p.GetType() == q.GetType()
are all true, but

  var r = q.x;
yields the compile-time error

CS1061: '(int u, int v)' does not contain a definition for 'x' and no accessible extension method 'x' accepting a first argument of type '(int u, int v)' could be found (are you missing a using directive or an assembly reference?)

and if

  dynamic d = q;
then

  var s = d.u;
compiles, but throws

RuntimeBinderException: 'System.ValueTuple<int,int>' does not contain a definition for 'u'

at runtime, revealing the underlying CLR type.


> tuples are "untagged", records/structs are "tagged" (the members have names).

Not always true in C#. Tuples can have member names

https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...


Going from C# to Typescript with type unions it always felt weird. Maybe that’s a good reason to have held off on this for this long? Know your audience and all that?

It does get used to but still reading the too many A|B|undefined gets tiring after a while. It also adds to laziness as you could take one thing and be fuzzy about returning a combo of three or more things. And as you go up the stack this gets more and more confusing.

C# forces you to deal with this in a constrained way which I like.

But perhaps there are folks who are excited by this that have a convincing argument? Please educate me.


As a long time C# dev, I feel like I’m missing something about this proposal. The use case for this doesn’t seem well defined to me. Could someone give me a real world example?

I’m pretty sure you can implement the example in this proposal by declaring an empty interface and having some record classes that “implement” it. Does that lose something that I’m not seeing?


That type of hierarchy is “open” in the sense that when you handle them you can’t know whether you handled all of them. Anyone can make a new implementation of your interface, potentially in code that isn’t yours.

Also, the options may not share any surface area at all, so the “interface” for all the types may be empty which is a bit unnatural in OO.

But under the hood, it’s just this: a type hierarchy. But the hierarchy is closed for extension, and (this is the key part you’d not get if doing this “manually” as you describe) when code uses the cases, failing to account for a case will cause an error at compile time.


I think the most down to earth example is the AST or data protocol examples. Take Jason for example. Instead of having a JsonValue class to hold the data you would have a Union type that is either string, bool, number, array of your union type or map type which uses a string as key and the same union type as value. It would also allow to implement result types like rust has them. So you could define an API that returns a value or an error. I didn’t see in the proposal if they talked about generics though. In functional programming world this is mainly known as algebraic data types. I can say that if you get used to model types like this you really start to miss it in languages that don’t support it.

[1] https://en.m.wikipedia.org/wiki/Algebraic_data_type


This[0] lib implements F#-style discriminated unions. The way I've used it is to make my outer layers return "Result" types that wrap the DTO, error, etc. under one umbrella. It allows for pattern matching your results which makes error handling easier and also allows your models to evolve over time without changing your function signatures at the edge.

[0] https://github.com/mcintyre321/OneOf


We have been using empty interfaces to simulate F# discriminated unions for the last 5+ years. It works like a charm. The problem "not all cases are handled in the switch" occurs very rarely (once per 50k lines of code) and, usually, we fix it after the first run of a smoke test. So, it's not a problem at all.

I think,.net team haven't added discriminited unions yet because it can be effectively simulated with interfaces.


A simple example is the Result<T> which could have either Ok(T value) or Error(string message). In order to get the value you need to switch over both cases, forcing you to handle the error case locally.


Like an enum, a union have a closed set of elements so it can be statically checked all cases are handled.

With an interface, someone could add a new implementation, causing codee to fail at runtime.


I found this video quite useful in showing the new proposal in action.

https://youtu.be/aksjZkCbIWA


Ha! Under covariance / contra variance

> Note: Have Mads write this part.

:)


Mads Torgersen, lead designer of C#, just to clarify lol


> The interior layout of the union struct is chosen to allow for efficient storage of the data found within the different possible member types with tradeoffs between speed and size chosen by the compiler.

Having _attempted_ (and being bitten by) far too much black magic with C# unions in the past (using FieldOffset), there is an unfortunate situation here: aliasing a pointer/ref value and value is UB. This means that a struct union of a u64 and an object would need separate fields for each, wasting 8 bytes. That is, unless the ryujit/gc is updated with knowledge about this.


Not UB, illegal. Per ECMA 335, II.10.7:

It is possible to overlap fields in this way, though offsets occupied by an object reference shall not overlap with offsets occupied by a built-in value type or a part of another object reference.

Per .NET 8.0:

  using System.Runtime.InteropServices;
  var s = new S { Bar = "Baz" };
  [StructLayout(LayoutKind.Explicit)]
  public struct S {
      [FieldOffset(0)] public UInt64 Foo;
      [FieldOffset(0)] public Object Bar;
  }
compiles, but throws

System.TypeLoadException: Could not load type 'S' from assembly 'foo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

Interestingly, this code elicits a warning

ILC: Method '[foo]Program.<Main>$(string[])' will always throw because: Failed to load type 'S' from assembly 'foo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because of field offset '0'

from the AOT compiler, but none from the C# compiler.


> from the AOT compiler, but none from the C# compiler.

The C# compiler has very little knowledge of `[FieldOffset]`. It's expected that developers understand the runtime implications of this.


I always think it's a shame that instead of C# becoming a better OO language, they keep trying to become an uglier F#. IE, why is the syntax for multiple dispatch still so clunky?

I know I know, pseudo OO took over the world, and then people revolted against it, so the easiest thing to do to stay relevant is to become "slightly functional with curly braces", rather than to actually try and do OO well.


I have the same objection but coming from the other side. The mainstream languages used to have pervasive mutation and side-effects. Now they have pervasive mutation and side-effects with lambdas.

But my question is: what is missing from the OO side? What would it take for C# and/or others to "do OO well"?


To be clear I don't really come from "the other side". I very much enjoy and like functional programming. But again, that's what F# is for, right? If we push the state of the art in another directrion, the whole landscape benefits more - because I think we both agree C# is never going to be a better FP language than F#.

As for what C# could do better, the OO solution to the problem pattern matching solves is multiple dispatch - with single dispatch languages you need things like the visitor pattern. Making that a first class part of C# would be fantastic.

https://shawnhargreaves.com/blog/visitor-and-multiple-dispat...


> What would it take for C# and/or others to "do OO well"?

Multiple inheritance. (j/k)


Unironically this.

Having "interfaces" and "abstract classes" is just a kludge, multiple inheritance covers all of this with one language construct.

The "diamond problem" is a C++ issue, every other language solved it.


The "diamond problem" isn't a C++ problem. C++ solved it with virtual inheritance.

Also C++ has exactly what you want. No interfaces, multiple inheritance. People don't utilize it unless it's a GUI lib.


I think they solved the problem by not supporting multiple inheritance, instead supporting interfaces and/or traits. But also "composition over inheritance" pretty much won, thankfully imho.



When in doubt look to CLOS.


Mixins via inheritance is a sweet pattern that I miss in C# sometimes.


C# has default interface implementations now, that can be used to create mixins...

https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...


Yeah I sometimes use them but they don’t cover all the cases. I’m waiting for roles/extensions to see if I can achieve a similar result.

https://github.com/dotnet/csharplang/discussions/5496


What does "better OO" mean here?

Which concepts or feature you have on mind?


multiple dispatch or whatever helps people stop thinking about a hierarchy of inheritance and more in terms of objects and messages.


I'm currently using nested records with a private constructor in combination with the nuget package https://github.com/shuebner/ClosedTypeHierarchyDiagnosticSup... to make sure the switch types do not require a `_` case. This is essentially the desugared version of their "Union Classes" proposal. This already works very well. Still, I like this proposal because it would be nice if the nuget package would become unnecessary and the syntactic sugar is also nice to have.


Do note that record types aren't closed even with a private constructor. The compiler will generate a protected constructor for copy operations which can then be inherited from. To prevent this, you have to define your own protected constructor and have it throw a runtime exception if it isn't a valid case.


You are technically correct (the best kind of correct), but in practice I've not found this to be a problem, certainly not something that is ofsetting the vast usefulness of this data pattern.


I've found the same to be true as well.


how do people writing C# deal with its gazillian features? It seems easier (and more useful) to learn and master every language that it absorbs, rather than C# itself.


regarding ad hoc unions the article mentions this:

  Siamese pet = ...;
  (Cat or Chihuahua) mostlyCats = pet;
  Dog dog = (Dog)mostlyCats;
Note: This works for implemented interfaces too.

I am unsure what that section entails...

Let's say I have a function like this:

  void ConsumeAB<T>(T ab) where T: IA, IB;  
And implementing classes like this:

  class ABC : IA, IB {}
  class ABD : IA, IB {}
Will I be able to use this function like this:

        var items = new (ABC or ABD)[] { new ABC(), new ABD() };
        foreach (var item in items)
        {
            ConsumeAB(item);
        }
In other words will the ad hoc union act as it would implement all interfaces that all cases have in common? My guess it won't work, as it gets lowered to code that uses object. Would a custom union help me here?


I very much hope to see this (or similar) in C# soon. It’s not make or break for me, but it would certainly help make for cleaner, smaller codebases — I’ve sorely missed it in the past, but only a couple times. My context is being a former hard-core C++ person who switched to .Net and AWS pretty-much cold turkey.

Edit: typo


That proposal doesn't mention how the union struct handles tearing under concurrent modification.

Tearing can cause memory safety issue. Variant A can have an integer field and variant B can have a reference field in the same offset. Tearing can cause it to treat an integer as reference.


I’m the author of the original issue — I agree, we’ll have to ensure the struct layout is solved. I think the only thing that makes sense is to just waste a little space and store the fields side-by-side. In almost all cases where people would use a struct I think this is an acceptable tradeoff.

At the point where you have more than 5 cases, the GC overhead starts to get shrink in comparison to the calling convention and copying overhead anyway.


Will there be any layout optimizations performed by Roslyn for fields that can be aliased? E.g.: for a type union which has variants with 2 object fields and one ushort-sized at most each, have a base layout of (object, object, short)?


Tearing issue is mentioned in this issue https://github.com/dotnet/csharplang/issues/7016 but not in proposal


The design is not going to allow for struct layout to have memory safety issues when tearing is involved. That is a problem we are very well aware of and consider in designs.


What's the difference between this proposal and sealed classes in Kotlin? Looking at the definitions in the proposal these look functionaly similar. Haven't worked with C# in some years so suprised that this isn't already a language feature.


What would the rough timeframe be for seeing adoption of this into the language?

I was considering introducing the OneOf library into our codebase, but if this is < a year or so away it might not be worth the effort.


You won't be able to leverage C#'s pattern-matching effectively with a library.

Really though, you don't need a library to do sum-types in C#:

It's best to just use records for now:

    public abstract record Maybe<A>
    {
        private Maybe() { }

        public sealed record Just(A Value) : Maybe<A>;
        public sealed record Nothing : Maybe<A>;
    }

The private constructor and sealed case-types stops anybody else deriving from `Maybe<A>`, so the type is effectively closed and finite.

You can then construct like so:

    var mx = new Maybe<int>.Just(123);
    var my = new Maybe<int>.Nothing();
Then you can use the pattern-matching:

   var r = mx switch
   {
      Maybe<int>.Just (var x) => x,
      Maybe<int>.Nothing      => 0
   };
Of course, we don't get exhaustiveness checking, that'll have to wait for the proper sum-types (although the compiler does some pretty good work on this right now); but until then this is the most expressive and powerful way of doing sum-types in C#.

And, if you want a load of pre-built ones, then my library language-ext will help [1]

[1] https://github.com/louthy/language-ext/


Not quite closed. The compiler generated protected constructor can still be used.

    public record UhOh<A> : Maybe<A>
    {
        public UhOh() : base(new Maybe<A>.Nothing())
        {
        }
    }

    Maybe<int> m = new UhOh<int>();


It's not a bad idea to evaluate OneOf. This proposal relies on records, so every use instance requires heap allocations and dereferencing which has memory and time implications respectively.

I implemented my own option types entirely differently via ref structs where the "non-option" becomes either a default of a value type or a null reference (size of a pointer). Then (in theory) the overhead is reduced to a single dereference with everything else being stack-allocated.

I then built the same thing as a non ref (regular) struct version for storing in a field. So I have Options.Quick and Options.Long namespaces with practically the same struct design just the former is a ref struct.

This has served me very well, is very fast. It just doesn't have the exhaustive checking and risks derefing a null reference, but in practice is not a big issue.

   if(option.IsT1){ doSomething(option.ValueT1)};
   if(option.IsT1){doSomething(option.ValueT2)}; // this would throw an exception, i.e. a bug, but its very readable to catch the bug


It's a different kind of ugly, but you can use the Visitor pattern to get the same exhaustiveness checking at compile time without a library.


There is no timeframe right now. At the moment this is a loose proposal for how to approach union like features in C#. There are a lot more details to dig into here before we'd be ready to move to scheduling and implementation. Also need to decide if we take on all of the union variants proposed or just some and what priority order to approach them.


The linked says "Proposed, Prototype: Not Started, Implementation: Not Started, Specification: Not Started"

.NET releases are every November, and I would be very surprised to see this in November 2024. More likely November 2025 at soonest. But check back to that page later.


Definitely no way for Nov 2024 since they're already no longer merging features for that release. Considering the scope of this, I'm doubtful for next year as well. .NET takes their time with features to get them right as well. They don't seem to take as long as Java, but maybe most new features are also not as widely publicized.


3 years away at least.


Even dotnet is looking at implementing type unions, why can't Dart finally agree on adding this aswell!?


We added sum types and exhaustive pattern matching in Dart 3.0.

Union types (what the proposal here calls "ad hoc unions") are a separate, much harder feature whose value proposition is less clear. The cost of adding a new kind to the type system is quite large because it impacts method resolution, overriding, type inference, subtyping, least upper bound, generics, type promotion, etc.

It can be worth it (for example, we added tuple/record types in Dart 3.0), but the value proposition must be correspondingly high to justify it. It's not clear that union types meet that bar yet. In many if not most of the places where users are asking for it, it often seems like what they really want is overloading or some other feature.


Thanks, I appreciate the explanation a lot! I find losing the type hints because I am forced to type something as "dynamic" is a bummer, I hope exhaustive pattern matching solves cases like these: https://github.com/pocketbase/dart-sdk/blob/master/lib/src/a...

Related issue: https://github.com/dart-lang/language/issues/83


Is this just a Variant like VB? (and VB.net)


No - because those aren't limited to just a few specific types.


OK yes of course, its like you can make your own Variant style types.


Does anyone also feel that any new feature in C# changes to a flame war between Java and C# :-) I expected that before clicking the link, and came prepared :popcorn:


Every time the question of preferred programming languages comes up, I'm usually in the extreme minority with my preferences being Rust (for small/fast) and C# (for productive and easy, where GC is acceptable), a combination that doesn't seem to appeal to too many people.

But for the projects that fall right about in the middle where it could go either way and I could see either language working, I almost always pick rust with discriminated unions being the biggest reason. It's incredibly hard to go back to a language without even basic ADTs after seeing the light.


I've been writing C, C++, C# and some js/sql/ts/python for money. Lua for lulz.

And *nothing* gets even fucking close in terms of productivity to C#.

Great language, mature and robust ecosystem with sane compilation times and

great tooling: package manager, test runner, strong debugger, one CLI with almost all tools needed.

I wish C++ was half as enjoyable as C# is.


I'm a Linux user who prefers open-source tools. I not a fan of Microsoft's programs, tools, editors, etc.

What would you suggest I do to be productive with C#?


If your goal is to avoid anything touched by msft employees, then the answer is you can't use C#. Just like you can't use Java if you want to avoid Oracle because of their predatory practices, or can't use Swift if you want to avoid Apple, or can't use Go if you want to avoid the AdTech company. In fact, just limiting to MS you probably can't use Java or C/C++ either as they have contributions to compilers for both.

But this is rather silly, so I'm going to assume the question is more reasonable "What can I do as a Linux user to have positive experience with C#?".

In this case, the response is very simple - you just install the SDK (sudo apt/dnf install dotnet-sdk-8.0) and then pick either VS Code and base (and free and MIT) C# extension (you do not need DevKit) or combine csharp-ls and Neovim. Other text editors that support LSP will also work. Nothing else is required.

Rider is suggested but it is absolutely optional, especially if you are not looking for that kind of high-tier IDE and prefer simpler (and faster) text editing experience.


JetBrains Rider


It's remarkably disappointing to see this question downvoted. I had the same thought, but this makes me wonder if it's just a bad culture fit from the start. I've been Linux-only now for 15 years and haven't used an IDE in well over 10. I use vim (neovim now) exclusively. Given my characteristics, should I just squash my curiosity and do a pass on C#? Do C# people not see value in open source? Is there low tolerance in that community for people who have those values?


The reason c# has such a weak open source system is two fold. Supply and demand. From a supply stand point source enthusiasts have historically hated Microsoft.

From a demand standpoint everyone uses the Microsoft variant because it's easy to use and hire for. There's a ton of documentation. Lots of issues are at the seems of different libraries, applications or frameworks you use. And in the Microsoft world it's frequently all the same. Everyone uses the same identity library, the same webserver, the same cloud, the same database, the same orm.

If you're into learning technology it's not for you. But if you're into learning about domains and business instead of tech or like having free time it's a great stack. Doesn't work well for b2c because of the cost, but works really well for b2b.


Strong advocate of open source here.

That said, in an enterprise setting, for all of Microsoft's other faults, C# is damn near bullet proof. Some enterprises, like mine, can't rely on community supported tooling for life-critical systems. If something breaks, we're near instantaneously on the phone to a dedicated support rep to fix the problem.

I learned C# and Java years ago during my IT degree but had long since forgotten them. The enterprise I work in uses C# a fair bit.

Only started to pick up C# again in the past few months, and holy heck, I forgot how pleasurable it is to work in C# again.

If the third-party components used in some software I help maintain supported JetBrains Rider, I'd use it in a heartbeat.

Visual Studio (not Code...) is okay, though I'm not tripping over myself to use it. PyCharm and IntelliJ have spoiled me.


C# has an official language server and an unofficial one (that unfortunately seems less maintained now). It works just fine with neovim.

Funny enough, it's the efforts of Omnisharp and the VSCode team that has so significantly improved the experience of using various other languages in open source editors due to the creation of LSP.


>Do C# people not see value in open source?

C# and dotnet are open source so what on Earth are you even talking about?

>squash my curiosity

Maybe increase your curiosity and try something new, change the 15 year trend and use an IDE. JetBrains Rider is great and cross platform.

"tolerance in that community for people who have those values"

More likely is they don't worry about this nonsense and just get work done, making a great language, platform and tools used extensively and productively in many companies. Why should they "think about you at all" as the meme goes?

Talk of culture, values, community, tolerance, "your characteristics". This all reeks of injecting extraneous personal feelings where they don't need to be. How about you just try the language, if you actually care to do so, and worry about the rest later?


JetBrains Rider and get over your hangups so you can be productive. Now, you'll have to pay these professional developers for their work making an excellent cross platform IDE for you but you can leverage their work (and Microsoft's great language and platform tools) for your own benefit so it's a win/win.

Beyond that, no offense but I don't think C# devs don't owe you anything, it's on you if you have criteria that excludes you from using all these awesome things.

If your religious beliefs are a sticking point, then so be it.


> And nothing gets even fucking close in terms of productivity to C#.

C# gets compared to java a lot for a good reason, and yet the languages and ecosystems have since diverged in their priorities and abilities. This gets even more complicated when considering F# and Scala. Do you think Java would offer similar productivity, or are there aspects to the C# that are—in your personal experience—uniquely productive?


I wrote c# professionally for 8 years and now have switched to a codebase mostly in Java and some kotlin. I know some of this is learning curve but none of those things mentioned are anywhere close in JVM. Build systems, package management, close IDE integration for test runners, builds, etc. The ability for the IDE to even know what version of Java you want the project to run in even though it's defined in the project doesn't exist. Kotlin as a language does some nice things but even the syntax highlighting is better with c#. Async is trivial in c#. The orm makes it so you can write db queries in c# with Linq and not have to mess with SQL. SQL injection isn't even a thing I ever had to think about but now I have to write actual SQL inline with my code to do any sort of non trivial queries. Were using maven which is apparently older than gradle so maybe that is better but having to remember do clean then compile before any type of build required action has caught me with so many things. Like migrations or tests including non existent files. There are more things that drive me crazy but this post will get unreadable and it's ranty enough as is.


I went from C# to JVM/gradle and have the opposite experience.

1) default C# IDE is junk compared to IntelliJ 2) Gradle give you insane level of control over project you can even build dynamic template tasks shown in IntelliJ UI using Kotlin/Groovy 3) JVM ecosystem is much bigger and default framework SpringBoot is ready to use with houndreds of integrations which are either non existent or hards to use in .NET as opposed to SpringBoot where you have batteries included approach e.g. want outbox pattern? Simply use modulith, convention over configuration will give you some nice defaults… 4) There are plenty of widely used langs compared to .NET if you don’t like modern Java (which is on the same level as C# IMHO) you have: Kotlin which is a better lang than C#, Scala, Groovy from the mainstream ones and Clojure and probably many more lesser known. 5) There is nothing as expressive as Groovy Spock for testing in .Net world.

There are issues I see but can be easy mitigated e.g. I don’t like default Java heavy ORM (especially mapping complicated two way relations) but it’s very easy to not use them and simply relay on mapping foreign key.

To sum it up I bet your issues are mostly you don’t know ecosystem well enough.


What makes intellij so much better than visual studio or vscode?


those are two separate questions.

Compared to old VS: First and foremost: feature richness of refactorings, code inspections and plugins - basically in the IntelliJ you have everything Reharper plugin can do for .NET and probably more. Second IntelliJ can run on non-spying operating systems. Third it's much more resource friendly for bigger projects. Last but not least ItelliJ is keyboard user friendly (e.g. you can jump and resize windows using only keyboard).

Can't say much about vscode vs IntelliJ. When I tried it last time Java plugin was not there. I could not use ItelliJ keyboard shortcuts - also code formatting and refactoring options were worse than in the itelliJ. That's to be expected it's paid product after all.


vscode is basically a bunch of different tools duct taped together and, as a result, constantly breaks. It's a lot closer to being emacs than it is to being an IDE (and I say this as a fervent emacs user outside of large projects). Even with the language servers, IDE functionality is going to be playing catch-up for the next decade. Intellij definitely has a leg up there.

I've never used visual studio before so I assume the comparison is going to turn up a different set of issues. However, being tied to windows is probably not a good thing in the long term.


This is definitely all just a learning curve on how to do this. None of this is accurate at all.

- IntelliJ knows exactly what version of Java you want to run and develop against.

- Async is also trivial. Just fire off a virtual thread.

- it sounds like your company just picked a bad orm. Any good one is going to escape sql injection when binding variables. You are using binding right? If you to “write it in Java” there is also jOOQ.

The one thing I do think C# is far superior at is packaging and distribution. It’s beyond simple to package a C# application into stand alone binary, whether is be AOT or JIT.


I'm not worried about SQL invitation so much as I'd rather just query my C# objects instead of writing raw SQL. Yuck.


Pretty much every language has some kind of monad-based query builder. The standardized syntax is nice though.


Java has that. It's just not the default choice because there is no default choice like in .NET. The options I know of: QueryDSL, jOOQ, Ebean with generated query beans.


The standard library alone is insane. The only time I need an outside library is for something specific like a database or sdk.


I've programmed damn-near everything at some point, with a long period of x86 assembler plus C++. Really unpopular opinion: VB.NET was the most enjoyable, most readable language I ever programmed in.

I've only begrudgingly moved to C# over the last few years as my primary since MS has pretty much killed VB.


I'm a big fan of C#, but to be fair nothing else you've named is really aiming for the same niche. The better comparison would be Swift, Go, Java/Kotlin, stuff like that.

But yeah I think C# is often unfairly viewed as a boring enterprise-y language when it's actually become a very comfy swiss army knife.


If it wasn't so linux distribution unfriendly…


I've found it quite easy to build and deploy to Linux, also curious what challenges you've run into.


If it can't be included in the distribution, it's not "distribution friendly"


Almost everything I build in C# runs on some flavor of Linux.


I said nothing about running. I said about distributions.


What distributions specifically have you had a problem developing on?

I've developed on Ubuntu and Pop_OS! in C# for years and never hit a problem.


Can I apt-get install your software? If not, then you've just hit the problem.


Oh yeah so unfriendly.

  sudo dnf install dotnet-sdk-8.0. 
  sudo apt install dotnet-sdk-8.0
  sudo apk add dotnet-sdk-8.0


apt install dotnet-sdk-8.0 Building dependency tree... 0% Error: Unable to locate package dotnet-sdk-8.0 Error: Couldn't find any package by glob 'dotnet-sdk-8.0'

Works like a charm.

Any more irrelevant things to say?


All this means is Pop OS doesn’t have it published. You don’t have to throw a piss because MS doesn’t publish it for Pop

https://github.com/dotnet/core/blob/main/linux.md#packages


I use regular proper debian.

The fact that it's not there (and it can't be there) was exactly my point.

I use toilets normally. I don't throw excrements.


I have no idea why he gave you some random link

literally first link under "dotnet debian" is:

https://learn.microsoft.com/en-us/dotnet/core/install/linux-...

__________

wget https://packages.microsoft.com/config/debian/12/packages-mic... -O packages-microsoft-prod.deb

sudo dpkg -i packages-microsoft-prod.deb

rm packages-microsoft-prod.deb

sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0


How is this "distribution friendly"?


In what ways?


Basically it's not included in any distribution. Mostly for license problems.

Which means anything you write with it can't be included in any distribution.

It's quite limiting. Basically it's only useful for proprietary software shipped by 3rd party.


The entirety of https://github.com/dotnet/dotnet (which is a VMR for full-source build of Roslyn, runtime and everything else that goes into runtime-only and SDK packages) is MIT, which is as permissive as it gets, save for CC0 or Unlicense.

Quick search shows that SDK and runtime packages are available in the official feeds for

Archlinux: https://archlinux.org/packages/?sort=&q=dotnet

Fedora: https://packages.fedoraproject.org/pkgs/dotnet8.0/

Ubuntu: https://pkgs.org/search/?q=dotnet

I always wondered what is the motivation behind the negative comments like these on .NET submissions. Is this because .NET is mostly made by MS employees instead of Google (which is an AdTech company) or Oracle (which has a license trap JDK distribution)?


> I always wondered what is the motivation

This is the motivation ↓

    apt install dotnet-sdk-8.0
    Error: Unable to locate package dotnet-sdk-8.0
    Error: Couldn't find any package by glob 'dotnet-sdk-8.0'


A lot of otherwise excellent software is not present in, for example, official Debian feeds. As a sibling comment pointed out, that’s no reason to throw a tantrum and is a trivially solvable issue.


Lol trivially… unless you want to distribute your software that is.


You can make a single file executable that does not require any dependencies with just the following. No runtime is required on the host, which I assume you allude to as lacking necessary package in the official feed might cause issues if it's required (and given it's present in everything that is not Debian, it is a Debian issue and something Debian must address).

JIT: dotnet publish -p:PublishSingleFile=true -p:PublishTrimmed=true

AOT: dotnet publish -p:PublishAot=true -p:OptimizationPreference=Speed*

Use -o to specify destination.

By default targets the host's OS RID, but can be overriden with e.g. `-r linux-musl-arm64`. Cross-architecture compilation is supported within OS, and there is a nuget package that switches the publish to Zig toolchain to allow publishing for Linux under Windows without relying on WSL2: https://www.nuget.org/packages/PublishAotCross This only concerns AOT as .NET uses the same linker to produce the final binary as your regular C/C++ code. It is a true native executable through and through that is understood by all standard tooling like native code profilers. For JIT binaries, anything that .NET supports can be published for under any other OS and ISA.

* my recommendation as the size impact is negligible but codegen quality in edge cases improves quite a bit. Other flags that may be useful are -p:IlcInstructionSet=x86-x64-v3 (AVX2 and friends) and -p:IlcFoldIdenticalMethodBodies=true (it's disabled by default because it can mess up stack traces, naturally, in .NET 9, disabling stack trace information enables it as well).


> You can make a single file executable that does not require any dependencies

Which is not distribution friendly. Thanks for finally understanding you were wrong all along.


> Which is not distribution friendly.

Why is that?


Can such a monstruosity be part of a distribution? No. Then...


What makes it a monstrosity? Why should it be (or not be) a part of distribution?


Don’t bother he is just saying things in bad faith.


I think you replied to the wrong comment.


There aren't many Microsoft products that seem to be genuinely good without many complications or drawbacks.

Most of those products have the words "Visual" and "Studio" in the name.

Atmel Studio can come too.


Typescript is pretty competitive with C# on those factors in my experience. What do you find yourself missing there?


The types are more expressive but it’s a weak type system under the hood (JS). The ecosystem is full of dependency hell and bloat. You’re also even further removed from the metal than what C# provides for.


It's possible to add DUs in C# today with some third party packages.

- https://github.com/domn1995/dunet

- https://github.com/mcintyre321/OneOf

Quite good and ergonomic with the source generators removing a lot of the boilerplate.

I have a practical example here using OneOf with .NET Channels: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...


Actually not a bad solution. Thanks for sharing


Instead of C#, why not F#? You get proper ADTs among many other great features, but you still get all the utility of the .NET ecosystem.


F# has it's own warts, tooling isn't as polished, stuff like type providers sound really cool but suck in practice IMO, file ordering being relevant for compilation is bleh...

Every time I try to use it I'm left with a feeling it's not worth the hassle over C#.

C# has been quite nice for 10 years and they keep improving consistently with every version.


Not to dismiss your criticisms, but I love that file ordering is relevant for compilation for one key reason:

It forces you to reconcile your architecture as it gets bigger and before you accidentally turn it into a monolithic nightmare. IMO, It's more than just a preemptive defense against cyclomatic complexity. Going into unfamiliar F# code, the file ordering is usually a huge clue where to start reading first.


The language itself is nice, the code bases that companies produce with it is not, and sadly that reputation plays into your decision to choose a career stack.

You're going to have patterns from the .NET Framework era being ported to .NET Core projects. It works, but you'll have two paradigms of doing things mixed into your project.

I envy people who only do hobbyist C# so get to work on code bases that have all the newest language feature usage.


>You're going to have patterns from the .NET Framework era being ported to .NET Core projects. It works, but you'll have two paradigms of doing things mixed into your project.

I've been spending the past couple years migrating various platforms from Framework to the new .NET and as long as you've got a head on your shoulders it's not too bad. Also, new projects in .NET are fantastic to work with, imo.


I have been doing the same, but I would be willing to bet that you're probably more disciplined than the average .NET developer (or at least have taken the time to learn more than just the surface features available). In my experience, most .NET developers don't take the time to really learn the framework (whether traditional .NET Framework, or the Core framework). It is a great feeling once you've got a Framework project migrated over to current .NET and everything is running along smoothly. My experience has mostly been migrating content management systems.


Genuine question, what do you mean by "learn the framework" ? (I mainly work with c#, I constantly worry I am not proactive enough in my learning).


I mean the built-in libraries for .NET (whether that is the older .NET Framework, or .NET Core - now labeled just as .NET). One of the biggest benefits to using C# and .NET is the amount of documentation available. If you are still using .NET Framework, and haven't moved to the open-source .NET, I would suggest spending some time learning the open-source version. It's not vastly different, and the good thing is the C# language hasn't changed very much, other than adding new features for developer ergonomics.


Thanks :)


I definitely dislike most C#/.NET developers/community (every time mediator is mentioned I want to stab myself) and would rather work with people in F#/FP.

But when you have to work on "diverse" development teams having some sort of patterns established (flawed as they are) brings some order to the insanity.


My brain really likes how organizes C# libs tend to be compared to the 50 different organization schemes I deal with in node and python


Does C# impose a lot of organization? I've only worked in one C# codebase but it has partial classes everywhere and TONs of abstraction bloat. I found it difficult to reason about the organization.


C# doesn't really, but VS + the asp.net framework do.

You kinda have to fight against the IDE to not do a lot of things a certain way. For example, it'll automatically namespace new files according to your folder structure. They've also turned a certain amount of automatic linting on to gently suggest/nag you to write in a particular way.Suggest you write classes in certain ways, use newer language features, declare variables in better ways, etc. You can ignore all the nagging, but it's also incredibly easy for the next programmer to walk in and use the quick actions functionality to 'fix' the code in a few clicks.

And the asp.net core team have been incredibly opinionated, forcing a lot of good coding practices on the community.

So on the plus side, they pretty much forced DI to be the way we all work now. It's worked really well. Most library authors have now embraced it with gusto and you'll have a hard time using new libraries in a code base that's DI incompatible.

The bad side is that sometimes they made bad choices, but they are more minor things. Like they bizarrely went all in on JWT tokens which work really badly for corporate apps. Or the IOptions pattern for global settings which sucks compared to normal env variables in any other language. Lots of confusion over how they work on forums and SO.


> You kinda have to fight against the IDE to not do a lot of things a certain way. For example, it'll automatically namespace new files according to your folder structure. They've also turned a certain amount of automatic linting on to gently suggest/nag you to write in a particular way.Suggest you write classes in certain ways, use newer language features, declare variables in better ways, etc. You can ignore all the nagging, but it's also incredibly easy for the next programmer to walk in and use the quick actions functionality to 'fix' the code in a few clicks.

This is why I find a .editorconfig file to be incredibly helpful.


I don't think I've really seen a partial class since the webforms/winforms days about 15 years ago. Maybe XAML too but I haven't used XAML in so long.

I think abstraction bloat is one of those things that's a preference. What's bloat to one is organization to someone else. When I hope into a python codebase and it's 900 line file doing computer vision madness, I hope and pray for abstraction bloat. I'm sure there are countless c# bloated codebases but I think that's mostly a function of c# codebases being inside megacorporate or government codebases. The bloat comes from the general inefficiency that those companies run at. I guess the same could be said by the scrappy python startups that put everything in a single file with no types or inversion of control or OOP.

I guess I'm saying that I'd rather deal with the bloat problem than the wild west problem. lol


Out of interest, what "patterns from the .NET framework era" do you think don't work well in .Net core?

( I'm someone who deals all day with legacy .Net framework projects, mixed with the kind of mix of .Net core 3, .Net 6, .net standard 2.0, and .Net 8 projects that you'd expect from a 20+ year old company with 260+ projects. And yes, I too envy hobbyists at time. )


>Out of interest, what "patterns from the .NET framework era" do you think don't work well in .Net core?

Overriding equals to do memberwise comparisons on POCOs

Any checking of types where pattern matching would be better

Old-style tuples without names (old style is tuple.Item1, tuple.Item2, etc.)

Checking of multiple tuple values at once without pattern matching

Any code that could be refactored with the new LINQ methods

Long ifs/switches that could be replaced by pattern matching

Concatenate a lot of strings (one per line) so there wasn't a ton of horizontal scroll, particularly for SQL in C# code

using statements still needing a level of nesting


Are the codebases produced by companies in other languages nicer?


Nothing against F# but the developer ecosystem around C# is just plain better, especially if you're already a dotnet shop. Documentation, code examples, tooling, developers who already know the language, etc.

F# doesn't really buy you anything if you're already invested in C# (or even VB.net for those poor souls) unless you have a very specific use-case for it, IMO.


The benefits of F# only really apply when using a 100% F# codebase.

Try doing WinForms in F#...


That F# has no native GUI framework is the reason why I stick with C#. When coding something with a GUI, the majority of my development time is typically spent in the GUI layer. The rest is some more or less straightforward object relational mapping with some validation an calculations in a business layer where I use Linq to emulate functional programming in C#.

Adding F# to the game would only complicate the scenario, because I would need to connect the F# interfaces somewhere to C# anyway, requiring an additional layer of mappings in many cases.



It is just a wrapper around Avalonia, which is written in C#.


It is (together with FuncUI Elmish) an extension for Avalonia to write GUI applications in F#. What difference does it make if Avalonia itself is written in C#?


I mean that's mostly pretty easy bindings, but yeah it's annoying that you have to do them and feels like you're losing out on the whole damn point of using F#


Indeed. The company I work for uses F# for large revenue workloads across many services; mostly coming from non .NET dev's. It handles large scale customer traffic quite well without a hiccup and with the C# interop we never find we are blocked (e.g enterprise/vendor libs). They found it easier to learn F# than C# - it has a lot more in common with JS/Go/etc in how code is structured and maintained from their POV. Good mentorship is critical for learning and deriving extra value from it especially if coming from old C#/Java/etc. The push for C# to minimal APIs, less classes, etc from our experience has slowly made it less necessary to have F# wrappers as well - the interop ugliness usually only resides in one file (e.g. ASP.NET Startup.fs or equilvalent in given C# framework). Given our proprietary nature however we don't broadcast a lot of our dev effort/development online - I assume many F# shops are/were similar.

We've created very large scale applications in it requiring significant throughput. One of its benefits is also its disadvantages - we typically are quite productive in it and so don't feel the need to hire as many people in the team. Before LLM's were a thing we found we were wasting less time with boilerplate in general than our C# code for example. YMMV.


I get the feeling F# is a second-class citizen in the ecosystem. How likely is it (or is it at all possible) that I'm going to run into some library that doesn't work with F#?


It's impossible for a .NET library to not work with F#. It's very possible (and even likely) for a .NET library to prevent you from writing idiomatic F#.

You're right that it's a second-class citizen.


> It's impossible for a .NET library to not work with F#.

This isn't true, C# has been adding new ABI features that didn't interact correctly with F# until the compiler and tooling catched up. For example, the spanification of C# was a huge a pain point and it still is when it comes to tooling.


TIL. Have links to any details about this? I'm very curious!


You mostly have to look at the time frames between C# adding features with new ABIs and the time it takes for F# to announce they can actually consume them.

For example, F# used to crash the CLR with InvalidProgramException when setting C# `init` properties. [1] It took almost three years, I think, to release the fix.

[1]: https://github.com/fsharp/fslang-suggestions/issues/904

Another rough example would be spans, and their more general feature, byref-like types (ref struct). These required plenty of compiler support, as they've got special lifetime rules (and more pending to implement `scoped`), they are banned as generic type arguments, and they require ignoring a special Obsolete attribute.

While these were added to F# timely, many language features still break when they interact with them: local functions, computation expressions (even the built-in ones), recursive type inference, and generic intrinsic methods such as `raise`, `defaultof` or `typeof`.

This wouldn't be so bad if C# hadn't "spanified" the shared framework and the entire ecosystem without CLS alternatives for many APIs.

So, these natural F# snippets don't compile:

  let numbers = [| for span in text.EnumerateLines() -> Int32.Parse span |]

  let checkNonEmpty (span: ReadOnlySpan<'T>) =
      if span.Length > 0 then span else failwith "Expected non-empty span."
Edit: And I haven't even gotten into the libraries that use reflection expecting you to declare types in ways F# doesn't support.


Stuff like Roslyn analysers, code generators and interceptors, only take C# semantics into account, the first one also works with VB.NET.

Since its introduction that CLR has the notion of CLS, the Common Language Subset, that any language targeting the CLR should be able to understand.

Anything in the MSIL or metadata, that isn't part of that, requires additional effort from the respective language to be able to expose those features, and many of the more recent improvements, are more in the sense of CLR meaning C# Language Runtime, than Common Language Runtime.


Just to add to the other comments, in my experience a large majority of libraries work fine. Maybe a little tweak here or there, or maybe you need to use some C# esq syntax (which is harder in my case because I so rarely do now).

Generally though, it's pretty easy. Sometimes you'll want to make bindings around various things (not that you need to, but it'll make things smoother).

It's not actually awful, it's just that there's no real documentation of "hey today we're going to take Serilog/whatever and show you how to configure it in F#" style videos, so depending on where your weaknesses lie it can feel frustrating.


My experience mirror yours. I really lean into the functional core, imperative shell because of this. Im mostly interacting with C# flavored libraries at the edges and I don’t try to force F# paradigms there, I just roll with the punches.


None, it's a CLR after all. That said many libraries may not utilize the best features F# has to offer.


Because nobody uses it, Microsoft doesn’t invest or promote F# heavily. I’m pissed about all of this, but I accept that eventually C# will get much of F#’s goodness.


I don't think that c# will ever get immutability by default though.


I’m in the same boat, and while I love the concepts behind Rust I’m not a fan of the syntax.

What I’d love to see is a hybrid between C# and Rust that has the capabilities of both languages in “layers”.

So business logic would be written in a simple GC variant of the language that feels like C# but the underlying standard library and low-level packages could use Rust-like semantics.

This is already the direction that C# is moving in: it’s getting more and more features related to “ref” and other low-level primitives.

Sadly Rust and C# have fundamental incompatibilities such as different string encodings, so they can’t be directly merged.


> It's incredibly hard to go back to a language without even basic ADTs after seeing the light.

How did we ever prioritize implementation inheritance over ADTs?

I don't know, but it was a mistake.


I think a similar but perhaps more accurate question is how did we prioritize virtual dispatch over ADTs?

And I think the answer has a lot to do with the expression problem [1] and which kinds of extensibility were needed in the kinds of programs that languages at the time were designed for.

OOP with subtyping and virtual dispatch makes it very easy to define an interface with a set of methods, and then have an open-ended set of concrete classes that all reliably implement those methods. It makes it easy to say "I don't know what data types I'll need yet, but I do know what operations I'll need."

ADTs flip that around. That make it easy to express "I don't know what operations I'll need yet, but I do know what data types I'll need."

For the kinds of simulations that Kristen Nygaard and Bjarne Stroustrup were writing, and then later the large GUI applications that C++ users were building, it seems that the former was more useful than the latter.

[1]: https://journal.stuffwithstuff.com/2010/10/01/solving-the-ex...


That's a good way of putting it.

Dynamic dispatch is for a fixed set of operations and open-ended data.

ADTs are for a fixed set of data and open-ended operations.


Probably bescuse OOP was a great fit for desktop GUI’s. This drove OOP into the mainstream. ML-style languages never had a similar killer application.


How widely understood and appreciated were algebraic data types at the time Cfront was first floated as the precursor to C++? To what degree had they shown up in mainstream languages?


Cfront being around 1983 would mean at least ALGOL68 and Pascal would've been well-known at the time. Ada is cited as an influence on C++. ML languages were definitely not mainstream but if Wikipedia is to be believed, ML was an influence on C++ as well.

The idea would've been well understood, but if the goal of C++ was "C with classes" or more accurately "C + Simula" then it just wasn't part of the goal. And of course we got 20 years of essentially forgetting that sum types existed outside of ML languages until they were "rediscovered"


Well if you want to compare to why C++ has multiple inheritance, you need to first explain which mainstream languages in the mid-1980s had multiple inheritance so we can contrast.

Like algebraic data types, multiple inheritance wasn't novel, but what were the big famous languages from that time which showed this was a must have for Bjarne's language?


That's not the comparison I'm making. It's possible for a proposed language to add something despite not being popular at the time, but that's not the default. I'm suggesting that if ADTs were not already popular at the time, it would be the unsurprising default for them to not be in a new language. In today's language landscape, by contrast, it would be surprising and disappointing for a new language to not have either ADTs or a specific alternative they propose for solving the same problem.


But the actual answer is revealing. Bjarne implemented multiple inheritance in C++ because it was easy. It's a New Jersey language. The priority is simplicity of implementation.

It's not about what was popular, or that Stroustrup was at Cambridge not Edinburgh (all the exciting Programming Language theory stuff in that era like ML was from Edinburgh as you perhaps know). If ADTs were easy, C++ would have ADTs.


For me its C# and Python, though I like Rust, D and lately been getting into Nim. I want to like Lisp, but its just not something I see someone paying me to work in.


C# for real work, Python for scripting or specific use-cases where it has excellent library support & Golang if I need something closer to C or very lightweight or easy for people to understand.

Rust is interesting but the compile times aren't great.


I use Python for real work too, but my current employer does not use it as much.


It's too dynamic/slow for me and the dependency system is a mess to the point where the entire Javascript world is better. Actually I forgot TypeScript, been using that for more than a decade now. So much better than the dark old days of JavaScript :)


For me it's Elixir / Golang and Rust. Between these 3 I can serve all needs.


I also really like rust's support for deriving traits. Being able to generate hashes, equality, debug strings, etc., is really convenient for hacking around.


I was disappointed with Rust not being able to do ad-hoc unions, as well as the flippant you don't need that mentality of the community.


You must really hate REPLs. You cling to your ADTs, I cling to my REPLs.


Is there a contradiction? I'm not aware about Rust or C# REPLs (a lot of results comes up, but no clue how serious are those), but GHCi surely proves that you can have both.


I've seen comments like this a few times, and I think there's a misunderstanding at play. I think people who are used to using REPLs look at people who aren't and conclude that, since they don't have a REPL in their toolbox, they don't have a tool in there that does what a REPL does. But I think most of the time, they are accomplishing the same tasks with a different workflow.

For instance, I use throwaway files instead of REPLs, even when I have access to one. I'm still running small and isolated experiments to build my understanding, but I get to use the editor I'm comfortable with and it's easy to copy the code into my project afterwards.

The other thing I used to use REPLs for was introspection/looking up documentation, which my LSP handles nicely. You can think of the LSP as a sort of REPL integrated seamlessly into the editor; when I have a question, I'll query the LSP by writing a line of dummy code that causes the LSP to generate an annotation with the answer. So for instance if I want to know if `foo` implements a certain trait/interface, I can write a variable declaration like `let x: Box<dyn MyTrait> = Box::new(foo)` and see if my LSP annotated that as a type error. I can do this very quickly and without leaving my editor, so it helps me stay in the flow of things.


There's a really nice VS Code extension called Polyglot Notebooks that's great for running small C# "scripts"


I can’t speak to Rust but there have been plenty of C# repls in the past.

https://fuqua.io/CSharpRepl/ For one


C# is great, likely the best mainstream programming language nowadays, but its in the hands of microsoft and microsoft didn't really care much about building a community or getting it to work natively in other OSes/toolchains.

Its a shame, they even had a second change when Oracle bought sun and no one knew what was going to happen with Java, but fumbled that as well.


> but its in the hands of microsoft and microsoft didn't really care much about building a community or getting it to work natively in other OSes/toolchains.

That statement was true - until 2016. Times change, Microsoft changed.


Yeah, really. I'm distributing a .NET 8 app across Windows, macOS, Linux and x64/arm.


Our developers write C# code using windows/mac/linux (it's up to their own preference) and we deploy to linux containers on AWS. Times have indeed changed.


A lot of devs are still living in the 2010s


Nah, people continue to pick Java or Go when they want to build their enterprise systems if they're not already married to Microsoft.

There's not a single widely distributed infra application in C# out there. When people want to build stuff like kafkas, kubernetes, consuls, they still go to Java/Go/C++.


> There's not a single widely distributed infra application in C# out there.

Azure has entered the chat.

Microsoft Orleans has entered the chat.

ASP .NET Core has entered the chat.

Ever played Halo online?

Used StackOverflow? (very likely)

Visited any website on Azure? (most likely)

Microsoft Orleans (Virtual Actor framework) powers the Halo server infrastructure since at least 2011 if not sooner, which arguably the Halo series is insanely popular, especially for online gaming. Also powers plenty of Azure itselfs own infrastructure. I've worked on dozens of scalable C# projects, and know plenty of devs from all over who have as well.

Orleans was so good, EA made their own version in Java which was extensively used there as well, though it looks to be dead, but Orleans is as alive as ever.

Not to mention plenty of Azure itself is open source, like say... Azure Functions, which run inside of ASP .NET Web Services... and are... you guessed it, C#. Plenty of services running on that platform.

https://github.com/Azure/azure-functions-host


Bing is well known for being Microsoft's 'dogfood' candidate for new versions of .NET.

https://visualstudiomagazine.com/articles/2018/08/22/bing-ne...

https://www.bing.com/version


I'll add Bitwarden and Jellyfin to the list of cross platform apps written in C# that are widely popular.


And all the high seas software (sonarr, radarr, etc.)


None of those are "widely distributed". They're all things that either MS themselves or MS-oriented shops run internally.

No-one's writing a database server or identity management system or source code repository or business intelligence tool or even something like a linter in cross-platform C#. Unless you're a Windows shop, it's just not a first-class ecosystem.


No linters in C#? That’s a good one.

C# as a language has always been cross-platform.

Though the last few versions have made incredible gains in performance optimizations. You won’t get the flexibility of C++ but the performance comes close to it in most cases, when written optimally.

And you don’t have to deal with arcane, bullshit build systems with a learning curve that is worse than the language, and breaks down on every OS major version update (or its own version updates).


> C# as a language has always been cross-platform.

Not really. Like, it notionally ran on FreeBSD, but nontrivial programs never actually worked until recently, and even now they mostly still don't.


The lack of an available runtime implementation, or the transitive dependencies of the dynamically linked reference assemblies, does not preclude the compiled assembly from being cross-platform. The assembly within the file built by a C# compiler contains IL code that can be extracted by cross-platform tooling. There’s nothing OS-specific at that layer.

We are talking about the language, not the runtime implementation.


We're talking about what language people use and why. How well it runs on their platform in practice is a much more relevant factor for that than how cross-platform it is in theory.


https://www.freshports.org/lang/dotnet

    pkg install lang/dotnet
Not everything works, and support is community-maintained, but “mostly doesn’t run” does not reflect actual state of affairs.


"Nontrivial programs mostly don't run" is my real experience. I was quite hopeful at first but the culture is very much to use windows libraries even when you don't really need them, and not really care about anything that isn't windows.


Do you have a specific scenario and a package name in mind when you say "Nontrivial programs mostly don't run"? This is the only way for this statement to be true. If it's something niche, the question is to the package maintainers, as usual, if it's actively maintained that is.

As a sibling comment pointed out, .NET assemblies within Nuget packages are by-definition portable, unless they use platform intrinsics without a fallback and or without using newer crossplat APIs, but this is predominantly specific to SIMD code and a rather niche case that concerns my work.

There are some Nuget packages which come packaged with native dependencies, and some of the ancient and unmaintained ones do come with like a single win-x86 .dll inside, but this is a "things you sometimes encounter with C/C++" and in general is a rare occurrence.

Nowadays, the packages that have native dependencies either simply expect the host to have them installed (like you install, say, gstreamer), or come with dependent packages per-platform (and the matrix gets quite big: [x86, x64, arm64] x [linux, osx, win]). Like https://github.com/sandrohanea/whisper.net

But then again, this has to do with a specific package and its owner, if that's what you meant.

Note that there is no culture issue, there is poor framing and a strawman one. "I had issues on a niche target" is not mutually exclusive with cross-platform. Would you say lack of support for SunOS is a damning evidence of "pro-windows agenda"? (whatever it is, I use macOS and Linux hosts mainly and surprisingly and funnily enough, there is work to support SunOS as well)

The thinly veiled "but linux/unix!" low effort replies are unfortunate and tiresome. They don't strike me as a productive attitude that seeks to make things work.


> Do you have a specific scenario and a package name in mind when you say "Nontrivial programs mostly don't run"?

Not a specific single program (not package) just my general experience of how "find useful-sounding program written in C#" -> "download it, try to run it" usually goes. I could probably dig up the name of the most recent thing I tried it with if you really want (I think it was a video renaming tool) but there's no useful discussion to be had about what a random 1-person project happens to support or not.

> But then again, this has to do with specific package and its owner.

> Note that there is no culture issue, there is poor framing and a strawman one.

If most package owners act a certain way that's what culture is. I doubt either of us has the time to do a statistically valid survey of what's out there. Maybe your experience is different, but let me ask: are you actually using C# programs on non-Windows, and e.g. reporting bugs in them? And finding maintainers take them seriously when you do?

> Would you say lack of support for SunOS is a damning evidence of an anti-OSS culture?

I think projects that assume the whole-world is Linux and don't support e.g. FreeBSD or Illumos (I assume that's what you meant by SunOS?) are very much anti-OSS, and complain loudly about them when I encounter them, yes. Obviously there are levels of "not supported" here, but "well, your patch sounds ok so I'll merge it, can't promise we won't break FreeBSD again in the future since we don't have the resources to test it" is still better than I've gotten from most C# projects when I've tried.


For the sake of productive conversation, I'm going to ignore irrelevant and imagined issues and will focus on the one that can be addressed.

What is the name of the software that you expected to run on the OS of your choice? I briefly looked through your Github profile hoping to find it but it seems it mostly consists of Scala, Java and a few other non-.NET projects. I assume you haven't had the opportunity to use .NET tooling yet. Are you interested in getting .NET SDK to run on FreeBSD?

Was "the evil" software just an ancient WinForms application?


> I assume you haven't had the opportunity to use .NET tooling yet.

I've used it some, not to the same extent, and generally at work - another one of those cultural differences I've found is that most JVM/etc. companies are at least theoretically on-board with open-sourcing their non-core libraries, whereas in .NET land there's much less willingness to do that. The actual tooling is good (the best I've used in some ways), but that's only one piece of the puzzle.

> Are you interested in getting .NET SDK to run on FreeBSD?

The SDK itself runs great. I'm more interested in being able to drop in a small program that solves a problem for me - like, that's the way I started with Ruby or OCaml or Go or Erlang, there was a standalone thing that I could pick up and use as part of my system, I didn't have to rearchitect everything or buy into the whole ecosystem or change my workflow. I don't even know Erlang at all, but it's running in my stack in that I'm running Riak (with the Java driver), and that gives me a place where it's already adding value and I could dip my toe further into it if I wanted. That's the thing I've never found from the .NET world - a reason to run it in my system that doesn't require me to fully buy into it to get started. (I guess in theory SQL server could be that, but it's non-opensource and doesn't have any compelling selling point over the substitutes. Part of it's honestly probably just marketing, but that goes for any new language/tool - the most persuasive thing is hearing other people having success with it in a similar situation)

> Was "the evil" software just an ancient WinForms application?

Quite possibly, but I remember command-line tools also not working - just these random one-developer tools have no reason to publish versions for other platforms, and it's not the natural workflow, so they don't. Probably the issues are minor (like, I remember one that was using some windows-only part of the standard library to do something incredibly basic like base64 decoding - I'm sure someone who uses .NET every day would take 30 seconds to swap that out with using the right part of the .NET core standard library instead. But it doesn't make for an easy onramp)


> If most package owners act a certain way that's what culture is. I doubt either of us has the time to do a statistically valid survey of what's out there. Maybe your experience is different, but let me ask: are you actually using C# programs on non-Windows, and e.g. reporting bugs in them? And finding maintainers take them seriously when you do?

Yes, I do, but only those that I personally encounter, which almost never happens, my daily driver is macOS but .NET treats it very similarly to Linux. Linux is the most well-polished platform though. I can't actually believe we still have to have this kind of conversation in 2024. It's like talking to a street madman who keeps asking "no but is the sky blue? are you sure?". Yes, it is.

And no, it's not something you have to actively think about - just send an http request, write to a file, render a button with avalonia, etc. None of these require thinking about underlying OS. It just works.

In fact, the first version which was called back then .NET Core 1.0 was released 8 years ago, and was already cross-platform. By the time .NET Core 3.1 got released, it was battle-tested and stable, many businesses were moving their applications to linux hosts, often to containerized deployments at the time. This was about 5 years ago. A blink of an eye for enterprise, but a long time ago given the development pace.

> Obviously there are levels of "not supported" here, but "well, your patch sounds ok so I'll merge it, can't promise we won't break FreeBSD again in the future since we don't have the resources to test it" is still better than I've gotten from most C# projects when I've tried.

FreeBSD support is "well, your patch sounds ok so I'll merge it, can't promise we won't break FreeBSD again in the future since we don't have the resources to test it". There are considerations to not break community-supported platforms though.

> I think projects that assume the whole-world is Linux and don't support e.g. FreeBSD or Illumos (I assume that's what you meant by SunOS?) are very much anti-OSS, and complain loudly about them when I encounter them, yes.

I'm sure maintainers appreciate it and are grateful for this kind of attitude :)


> I can't actually believe we still have to have this kind of conversation in 2024. It's like talking to a street madman who keeps asking "no but is the sky blue? are you sure?". Yes, it is.

I mean, it's the same experience for me the other way round. Every few years someone tells me "yeah, .net works properly on non-windows these days", I file that away in my head, a while later I stumble across a C# tool for something I want to do, I try to run it on my machine, and it doesn't run.

Like, I do believe it's getting better - one of the things linked in this thread actually had Linux installation instructions in the README, which is the first time I've seen that - but in most cases there's no real push or reason to move. It reminds me of the move to Python 3, which didn't really gain momentum until 3.4/3.5 when there were new features that gave an actual motivation to switch.

> In fact, the first version which was called back then .NET Core 1.0 was released 8 years ago, and was already cross-platform.

Yeah, I remember the excitement of that happening, and then the awkward realisation that no existing .NET software was compatible with it. MS pushed out SQL Server remarkably quickly, but there was never really a reason to run it (and while I don't know their nominal support status, it certainly never felt like a first-class platform) and they didn't really follow through. E.g. I don't think Visual Studio proper ever made it over?

> By the time .NET Core 3.1 got released, it was battle-tested and stable, many businesses were moving their applications to linux hosts

OK that is more interesting and promising.

> I'm sure maintainers appreciate it and are grateful for this kind of attitude :)

Meh. Linux monoculture is just as bad as Windows monoculture, the point of open-source is to make it so that people can actually modify and improve your work and use the modified versions, not to publish the code and call it a day.


You really should google before you make remarks... I'm also going to ignore the widely distributed remark because it is ridiculous.

> No-one's writing a database server

RavenDB, which can be run in a cluster, and uses less CPU (and I've read memory) than MongoDB itself.

There's also a recent C# based redis drop-in replacement that outperforms redis.

See more: https://github.com/quozd/awesome-dotnet?tab=readme-ov-file#d...

As for identity, you sound really silly when you consider it is literally built-in to ASP .NET itself, and can be expanded upon to your hearts desire, plus there's no shortage of available identity management libraries in C#.

As for source code repository, I'm not sure what you mean, but for years Microsoft hosted CodePlex which was essentially GitHub for C# projects. Visual Studio and C# projects are typically hosted on Git, I would be shocked if there's no web UI for git in C#, there's definitely plenty of applications for git in .NET

I'm not even going to comment on business intelligence, now you're just being very lazy. You're telling me Microsoft has 0 BI tooling and SDKs? Come on.

As for a linter, you are either trolling, or just blindly hate Microsoft for no rhyme or reason, there's more than enough linters, especially the best one of all, ReSharper, which is integrated into Rider which is a cross-platform IDE. There's also MonoDevelop, SharpDevelop, etc

I literally spent a few weeks ago porting a .NET 3.5 app I hadn't touched in years, to .NET 8 from Ubuntu Linux, and got it running, in under 30 minutes in Project Rider.

Also nearly forgot. Netflix's original web UI was Silverlight, which is C# / .NET back when it was at its peak for its time.


> RavenDB

Ok, that actually sounds interesting. Still haven't heard of anyone using it, and closed-source makes it a non-starter, but I'm vaguely interested. Even then though, sounds like their client libraries are only .NET?

> As for identity, you sound really silly when you consider it is literally built-in to ASP .NET itself, and can be expanded upon to your hearts desire, plus there's no shortage of available identity management libraries in C#.

Sure there is plenty of support for it within the platform. My point is no-one's writing their organisational SSO system in C#, unless they're already bought into the Windows stack.

> As for source code repository, I'm not sure what you mean, but for years Microsoft hosted CodePlex which was essentially GitHub for C# projects.

I mean the equivalent of Bitbucket Server or GitLab, the thing you'd run to host your own source repos internally.

> I'm not even going to comment on business intelligence, now you're just being very lazy. You're telling me Microsoft has 0 BI tooling and SDKs?

No, I'm saying that what they have is all locked into the MS/Windows parallel world stack. There's nothing that a company that isn't committed to them would use.

> As for a linter, you are either trolling, or just blindly hate Microsoft for no rhyme or reason, there's more than enough linters, especially the best one of all, ReSharper, which is integrated into Rider which is a cross-platform IDE. There's also MonoDevelop, SharpDevelop, etc

There are plenty of linters for C#, sure. But no-one would write a general linter in it. It was an example that came to mind because that was how Facebook first started adopting OCaml - a linter is a small standalone tool, so it's an ok place to experiment with a new technology stack.

> Netflix's original web UI was Silverlight, which is C# / .NET back when it was at its peak for its time.

And look how that worked out for them.


> And look how that worked out for them.

Worked out pretty well to be fair, their old UI actually worked perfectly on Linux via Moonlight. Moonlight was more usable than Flash on Linux which was an unstable mess.

> There are plenty of linters for C#, sure. But no-one would write a general linter in it. It was an example that came to mind because that was how Facebook first started adopting OCaml - a linter is a small standalone tool, so it's an ok place to experiment with a new technology stack.

Except ReSharper lints for other things too.

> I mean the equivalent of Bitbucket Server or GitLab, the thing you'd run to host your own source repos internally.

That was CodePlex, there's also Azure DevOps? Which does all the things BitBucket and GitLab do and probably more? It is arguably poorly named in my view, since I've heard managers confuse "DevOps" and "Azure DevOps" by using the term interchangeably.


> Except ReSharper lints for other things too.

Up to a point - it lints the things you might need in a C# application. But it's very much a tool for the MS/Windows/C# vertical - even using it in Rider isn't really their focus. If JetBrains was building the linter they used for all IDEA-family IDEs in C#, that would be interesting.

> Azure DevOps

Right, that's the kind of thing I'm talking about. Is anyone using that who's not already bought into MS/Windows/C#, is that a market they sell to at all? Can you even run it on anything other than Windows? It sounds like not, which rather proves the point.

"microsoft didn't really care much about building a community or getting it to work natively in other OSes/toolchains" still rings true IMO. C# has some great stuff if you're fully onboard with the MS stack, but they've taken at most baby steps towards fitting into other environments. (If anything it feels like they expect the rest of the world to fit in with them - if you want to bring e.g. Postgres into your MS/Windows/C# world that's relatively well supported, but going the other direction is much less so)


Rider has the same analyzers as ReSharper, likely more (I haven't touched the latter in ages).

Moreover, the ecosystem mainly gravitates to Roslyn analyzers which run within build system and, naturally, integrate with Roslyn LSP. They work regardless of IDE or text editor you choose.

In fact, quite a lot of them come out of box, with the basic set enabled by default to prevent you from obvious mistakes, automatically fixing code to use terser syntax or avoiding footguns when using low-level APIs where applicable, and a lot more opt-in for a specific scenario or a workload.


> There's not a single widely distributed infra application in C# out there.

If you’ve used Wi-Fi in a hotel it’s very likely you’ve used a C# app. Is that “infra” and “widely distributed” enough?


This comment is kind of out of the blue because no one in this thread is advocating for using C# for infrastructure projects.

Java is frankly a bad choice for that too these days for a new project, and for the same reason: why would you develop infrastructure in a language that requires shipping a runtime when Rust and Go exist? That made a lot more sense back when C/C++ was your only other real choice.

Meanwhile, plenty of people are in this thread telling you that they do in fact use C# to distribute cross-platform applications, but you seem to be uninterested in hearing that.


Java has been fine for infrastructure projects. There is so much out there that relies on things like elasticsearch, Kafka, or any aws service. Heck even the columnar dbs like pinot and Druid are most likely more used than clickhouse.

C# is also fine for this, just way less popular. Recently ms put out https://github.com/microsoft/Garnet when the redis debacle happened.

The Apache project has helped put out quite a few large infra projects, most of them in Java (I don’t know why this is but assume they have lots of resources to help with that, and also that people don’t like debugging memory crashes).

Ms has just been not the greatest collaborators with their open source community. A lot of the time it seems like they want to do the initial foundation, then leave “drawing the rest of the owl” to the community, which ends with lots of partially functional things. GUI frameworks, f# tooling, drivers for commonly used critical infra. At the same time, people get upset when ms tries to build a better version of an existing open source project, so there’s no winning. They’ve just put themselves in such a poor spot.


ClickHouse is more widely used than Pinot or Druid based on any possible metric.

If you try to install any of the latter, it will be apparent why. (They are poor in usability).


C# recently came out with Native AOT (ahead of time) compilation that compiles your app to a binary that can run without the .NET runtime (!!), and has fast startup and lower memory overhead. There are a few drawbacks: many reflection and run-time code generation and loading features are unavailable, and LINQ expressions must be interpreted which makes them slow. But it's easy to work around those limitations for a greenfield infra project, and C# has great performance characteristics.


It's even worse, plenty of Azure itself is C#, and I don't mean a few dashboards, I mean the infrastructure itself, heck the serverless infrastructure's open source and its basically C#. I'm sure the same could be said of AWS and Java (making an assumption), or Google Cloud Platform and Go / Python.


Ironically, given the past history that lead to .NET existence,

https://devblogs.microsoft.com/java

https://www.microsoft.com/openjdk

https://code.visualstudio.com/docs/languages/java

Because it turns out, making Java running on Azure, on those 60% Linux workloads, is lot of money.

Also plenty of Azure, anything CNCF related, is mostly Go and Rust.

Which is kind of sad, I would expect Azure to be a good contributor for having a .NET presence in the CNCF project landscape.


Fully agreed! It's really interesting how diverse their entire platform really is in the grand scheme of things. One thing I had hoped to see is one of those IL conversion projects that can make JVM bytecode and .NET bytecode co-habitate would have taken off slightly more. I guess the only real way to make those work is to either target both for WASM or implement the standard library of the respective languages in the target platforms, which is a can of worms.


A very common modern setup is PostgreSQL, C#/.NET, Linux & using JetBrains Rider for the IDE. You don't have to go all in Microsoft to use C#, F# or .NET.

Also .NET has been about "run everywhere on any platform" as their tagline for quite a few years now.

They have had plenty of community fumbles without question. I can't speak to those though. I've seen lots of vocal high up Microsoft employees try to win those fights on the side of the community but no idea what happens internally.


    A very common modern setup is PostgreSQL, C#/.NET, Linux & using JetBrains Rider for the IDE
You just described the startup I'm at. All devs are on Arm64 MacBooks and we deploy to Arm64 AWS T4g Linux instances. I'm all VS Code, others are primarily Rider.

.NET is a highly underrated platform for backend; it always puzzles me when teams think about moving from TypeScript to Rust or Go instead of C# because it seems a much smaller leap from TypeScript.


I'm itching to try and deep dive into go as a C# dev. I'm getting sick of enterprise C#, even if it is .NET8

I guess the grass is always greener somewhere


Worst part for me about Go is how exceptions are usually basically handled the way that they would be with a Result<T> in Rust, but without an actual Result<T> type. That and how nulls are handled, feels like the worst of both worlds.


Maybe startup C# feels very different; it feels much more like TypeScript and I more or less write C# like I would write TypeScript.


This comment feels like it's been written 20 years ago. C# runs natively anywhere and this has been true for at least a decade.


I'd like a supported port of .NET to the BSDs.


There is one: https://www.freshports.org/lang/dotnet

    pkg install lang/dotnet
Note that not everything will work, and it is community-supported. But the effort is there, and there are contributions to dotnet/runtime from time to time to further improve or fix it. It's in a better state than it was 2 years ago.



This always shows up. Comical.


Sorry, I am not very familiar with the .NET ecosystem. Are you refuting any of their points?


Come again?


I don't have anything insightful to add, I just want to add my voice to the choir of people saying that after using sum types, working in a language without them feels unnecessarily restrictive and awkward.

If this feature were implemented, it would take C# to a top contender for a daily driver language for me, and would make me feel a lot more confident in choosing it for projects where the C# ecosystem is already present, such Godot's C# version.

I'm really excited about this proposal, and I don't know anything about C#'s development pace, but I'll be watching from the sidelines hoping I can use union types in production in 2025 or so.


I find it strange and slightly confusing that tey call it "union types". The correct term would be "sum types" or "discriminated union types", union types being the union of two types with no distinctive tag between the two cases. E.g. |Int ∪ Int| ≡ |Int|, while |Int + Int| ≡ 2 |Int|


> I find it strange and slightly confusing that tey call it "union types". The correct term would be "sum types" or "discriminated union types"...

You mean like this?

> A proposal for type unions (aka discriminated unions) in C#.

Discriminated unions are a type of union type, hence the name, and in terms of how they're used in everyday development they fill a very similar role. If they have no intention of supporting pure TypeScript-style unions (which I'm sure they don't) then I don't know what the harm is of shortening "discriminated union" to "union" in the context of C#.


I've read the proposal, and I'm just pointing out that "aka discriminated unions" is incorrect or at the very least confusing. Call it "sum types" if you don't like the term "discriminated union types", it's both correcter and shorter than "union types"


AFAICT from a quick skim of the proposal is actually includes both sum types (called union types) and union types (called ad hoc unions) under the same proposal. This is, to me, very confusing. They work in very different ways.


At runtime every value of any of these types are tagged in some some way. The struct based ones with an explicit tag member that is not visible at the language level. All the rest by way of the object's runtime type (v-pointer).

Which means the fact that some look at a language level like a traditional closed sum type, and others look more like a union type is pretty much just that, looks. Even the "ad hoc unions" are basically functioning as sum types here, just an ad hoc sum type whose type constructors are other types, and abusing the fact that classes or boxed structs all have a vptr that can be used as the discriminator.

This all compiles down to code that pattern matches on either the explicit tag member, or on the runtime type, and after the pattern match you basically have a normal type to work with. Hence why they are lumping it all together.


The proposal does both sum types and union types.


It’s ironic when things like this get added back into C-styled languages that were intended to be easier than their predecessors.

Seems more confusing than useful. Wish they’d stop adding onto this ever expanding language.

It’s like these people get bored of plain old functions and objects, read an academic paper and go “i think I’ll add that next”.


> Seems more confusing than useful.

Are you suggesting that these design decisions make it through the entire process and into the language without the feature bringing a clear advantage in its use case? What would you say are the worst two or three of these C# features approved in the last decade?

Let’s assume for the sake of argument that you are correct though. The Roslyn compiler diagnostics are second to none, and pretty much any of these features will have corresponding analyzers to help remove some of that confusion, with documentation links being automatically provided if used incorrectly.

These guys aren’t writing feature proposals the way you write comments.


> These guys aren’t writing feature proposals the way you write comments.

You’re comparing the effort of a mainstream language design proposal to a pseudonymous internet comment, wow, just wow.

Do you truly know real software developers using any these new features? I must not be working anywhere cool because I’m always having to explain what they mean in code reviews.

Imagine writing a full C# compiler in 2024, imagine if one possibly could with all these different features in this big big language.

> Are you suggesting that these design decisions make it through the entire process and into the language without the feature bringing a clear advantage in its use case?

I’m suggesting they don’t end up being adopted by enough people to be convincing. To me that leads to the shared problem with cpp, feature bloat. Multiple ways of achieving the same thing.

Since you asked, these are the three off the top of my head.

1. Init keyword is strange, why specify an accessor that only seems to apply during initialization? We already have read only for constructors. I always felt initializing objects with brackets (whatever they hell they call it) was something meant for more casual code where assignment isn’t enforced or restricted.

2. Switch expressions are ridiculous. They can get hard to read, alienating non C# programmers who come from C styled backgrounds, part of what made this language successful to begin with. They seem more like yet another syntax sugar we have to learn. And to your Roselyn comment, a language shouldn’t be dependent on Roselyns analysis to actually be usable to the general public. That is not what I’d call a cross platform language, that’s a monopolistic one where Windows/Visual Studio is the only place a not .net fanatic such as yourself can easily get work done.

3. Nullable Reference Types further over complicates what used to be a simple system of reference and value types. On top of the already head scratching nullable value types which made people question why they initially moved away from javas only reference type concept to begin with.

Slightly off topic but we have at least 3 ways to define record types: tuples, structs, and now records. I actually do like record types but unfortunately now existing code bases are polluted with the former. The point I’m trying to make in this last statement is I feel they aren’t careful enough with their proposals. They got it right with records, but look what they brought us along the way.


Appreciate the earnest reply. This is a much better discussion now.

> Do you truly know real software developers using any these new features? I must not be working anywhere cool because I’m always having to explain what they mean in code reviews.

If you mean 9-5 devs, I am not even afforded the luxury of working with C# at all! Haven’t been since before .net core. I do ruby and devops. That being said, I have a side project with dozens of real users, closing in on double digit “market” share. I’ve needed to do some serious performance optimizations (it is a fork). The ValueType flexibility makes it easier to do these improvements.

Init applies to properties, readonly to fields (and methods in structs, or valuetype parameters).

The switch expression is something I’m also not sure about yet, but makes for less clutter in the editor. I think it’s part of a general strategy of providing the language with more compact syntax options. It reminds me of the pattern matching in F#. Did the language need those? Not really, but my biggest annoyance with older C# was the verbose syntax, and it factored into my perception at the time that it was a dinosaur language.

Roslyn is open source, cross-platform, written in C#, and can be forked. The analyzers can be linked as dependencies to any source project. I don’t understand the VS/Windows connection. They are just basically hooked methods that run during compilation steps and can emit compiler warnings, errors, suggestions, or automatically fix code. I think you are shooting yourself in the foot if you throw that feature out on pride.

Nullable reference types are optional on the file or project level and function more as type assertions, and don’t represent separate classes. I understand the frustration with it though. I think the language needed this in order to be able to statically analyze the expressions for nullability instead of relying on runtime checks. Those runtime checks are notoriously unreliable as a safety net because you have to remember to use it, and make a decision on using it. They made a tough call but I think it was the right one.

Structs are not records and are in fact mutable by default! It can be converted to a ref and then mutated, without unsafe code. The readonly modifier prevents that. I haven’t studied the nuances of readonly structs vs record structs yet. It’s considered a bad practice to use mutable structs (and undermines compiler optimization) but if you’ve ever had to refactor a class to a struct, with hundreds of usages, having the mutability can make the task much easier to do incrementally.

I think they are careful, but the decisions are very difficult and sometimes it’s a loss in either direction. C# prefers to change with the times but remain backwards-compatible, but I don’t envy the jobs of the language committee.


This has been the most requested feature by customers in C# for I-don't-know-how-long now.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: