Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Granted, I havent' used either for a couple of years, so my knowledge is a little rusty. Yes, some of them are "just syntax sugar", but man oh man does it make C# such a pleasant language to work with.

Used to work at a company which had services both in Java and C#, so some of Java's decisions or indecisions felt like pain points when switching between the two:

- Proper IEnumerable with proper iterators that in turn enables Linq (but in general permeates everything and is insanely easy to use and build upon). E.g. building an async service that behaves like an IEnumerable? Implement two methods.

Collection is halfway there, but I honestly cannot remember what was irking me about it in comparison to C#.

- Properties. Yeah, yeah, sealed classes, records and all that. Often you still need plain old classes.

- object initialisers. Which makes constructing anything a breeze. And on top of that you don't need manual .of methods for anything Colleciton-like if it'sa an IEnumerable.

- extension methods.

- named and optional arguments in functions

- null coalescing operator

- generics over primitive types (unless it was already implemented, I remember seeing a JEP about it)

- async/await. Yes, I know: different approaches to concurrency and all that. A lot of unnecessary verbiage could still probably be hidden behind a friendlier syntax.

- (sadly impossible in JVM to type erasure, only including this because I remember needing it many moons ago) generics metadata in runtime

- .... definitely a bunch more I don't remember at this point ...

 help



At the same time, all this syntactic sugar makes the language's surface area gigantic. Like it's almost C++-level complex, and then you would have to properly understand all the interactions between this matrix of features.

That's absolutely a valid language design and many people prefer that, but I personally prefer a bit smaller language with a bit more IDE auto complete, but where you never have to think about what exactly does a line do.

(And then there is also Go that falls off the other edge of the cliff with useless if err checks spamming the code making actually functioning error handling hard)


> a bit smaller language with a bit more IDE auto complete, but where you never have to think about what exactly does a line do.

If you need IDE to autocomplete, then you definitely spend more time to understand what a line does ;)


While typing this comment I pressed/swiped on several words to finish them.

One reads much faster than they write, I don't really see a problem with easily guessable auto complete.


Even if you read much faster than you type, it's easier to read fewer lines of code than dozens of plumbing boilerplate.

Sure, we can find factors for "verbosity increase" and "difficulty to understand a line" where what you say is also true.

I think that java found a decent spot where both factors are low. It's not a particularly verbose language, especially in modern times (records, type inference, I even mention lambdas because some people live in caves), like if you do POJOs and stuff with getter setters than you get a no information increasing extra 3+3 lines of code per field and that's most of it.

Method bodies are not more verbose than other typical languages.


> Proper IEnumerable with proper iterators that in turn enables Linq

Are you referring to generators?

> Properties

As far as I'm aware, it is a deliberate choice not to implement them, and I can see their point of view.

> object initialisers

I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.

> extension methods

Typeclasses are currently being explored, which are a superior approach.

> named and optional arguments in functions

Those would be nice (at least named arguments). I can see how optional arguments could complicate things.

> generics over primitive types

As you mentioned, it's in the works

> async/await... verbiage could still probably be hidden behind a friendlier syntax

The approach they took does not need any extra syntax.


> Are you referring to generators?

Both I guess.

Main thing is https://learn.microsoft.com/en-us/dotnet/api/system.collecti... which seems to be everywhere in the language and the library.

> I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.

This is mostly due language design. Java heavily relies on properties and provides no facilities for them. Hence the builder pattern instead of object initializers.

In C# object initializers synergize with properties: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...

> The approach they took does not need any extra syntax.

You mean it needs 15 lines whete C# needs one? ;)


> You mean it needs 15 lines whete C# needs one? ;)

Can you elaborate? The async/await approach is much more than 1 line when you need to switch a call between them. Whereas the green thread approach does not need anything.


What's Java's equivalent of

   x = await someFunction()
   await waitForSomeOtherFunction()
(both are async)

As mentioned:

var x = someFunction() waitForSomeOtherFunction()

That's semantically equivalent in the two languages.


Good list.

- Value types is a huge one to add if we're looking at what is actually in the wild.

- Scopeless `using` declarations are nice for RAII like behavior.

- IMO C# builds are actually way way nicer than Java. Sln and .csproj files and nuget are actually a lot easier to deal with than javac/ant/mvn/Gradle. Maybe that's more a .NET thing than a C# feature.


> - object initialisers

Probably not a good idea since they break encapsulation by exposing internals of the class. There is work on withers, which should make defining builders far simpler.

> - extension methods.

They make code harder to understand. If they ever come they would have to be declared at the top of each source file.

> - null coalescing operator

Maybe we'll get it, maybe not, but they want to first introduce proper nullable types, lest there is a risk of painting themselves into a corner.

> - async/await

There is a fork in the road, and Java has gone into the direction that leads to virtual threads and Structured Concurrency, for the simple reason that there is no simpler syntax than plain old synchronous code.

> - ... generics metadata in runtime

There are plans to add a kind of reified generics, so maybe we'll get it.


> Probably not a good idea since they break encapsulation by exposing internals of the class.

And thousands of manual get/set functions don't?

Thousands of lines of builders don't?

Object initializers are that plus much better handling of fields/properties that doesn't require hundreds of lines of tedious manual code: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...

> for the simple reason that there is no simpler syntax than plain old synchronous code.

But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...

Java always opts out for "let the developer handle all the complexity all the time even for the simplest most used parts of the code".


> And thousands of manual get/set functions don't?

My statement doesn't apply to mere data carrier classes. Anyway, getters and setters are an antipattern as well since one can just as well make all the fields public.

> Thousands of lines of builders don't?

With withers most of these will go away. And a class will be able to choose which things can be set, which is not the case for initializers.

> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and..

That code won't look that much different with async/await.


> Anyway, getters and setters are an antipattern as well since one can just as well make all the fields public.

I Java? Yes. Because of the language design, and not because of some inherent "encapsulation" or something.

Somehow `x with { a = b }` doesn't break encapsulation and offers nice DX. But object initializers? Lol

> And a class will be able to choose which things can be set, which is not the case for initializers.

The class in C# can easily chose what can and cannot be set. Because unlike Java it actually cares about things like this.

Quick example

    class Test {
        public int x { get; set; }
        public int y { get; }
    }

    var t = new Test{ x = 1, y = 2 };
I can give you a hint: this will not compile.

Another example:

  public class Matrix
  {
    private double[,] storage = new double[3, 3];

    public double this[int row, int column]
    {
        // The embedded array will throw out of range exceptions as appropriate.
        get { return storage[row, column]; }
        set { storage[row, column] = value; }
    }
  }

  var identity = new Matrix
  {
    [0, 0] = 1.0,
    [0, 1] = 0.0,
    [0, 2] = 0.0,

    [1, 0] = 0.0,
    [1, 1] = 1.0,
    [1, 2] = 0.0,

    [2, 0] = 0.0,
    [2, 1] = 0.0,
    [2, 2] = 1.0,
  };
Incomprehensible abilities for Java-land

> That code won't look that much different with async/await.

Lol. https://news.ycombinator.com/item?id=49726868


You can go with a lot less code if you don't care about proper cleanup of resources.

> And thousands of manual get/set functions don't?

By definition, they don't.

They are verbose and hard to maintain, but if ever in the future you would want to keep the same API surface but change the internal implementation detail, they let you.

> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...

No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds


> but if ever in the future you would want to keep the same API surface but change the internal implementation detail, they let you.

So do properties in C# which object initialization relies on. With significantly less manual code, or the need for tedious builder chains and withers.

`{ prop = x }` is no more encapsulation breaking than ` .setProp(x) `, but actually makes developer experience better.

> No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds

What's Java's equivalent of

   x = await someFunction()
   await waitForSomeOtherFunction()

If the two calls are sequential then simply:

  var x = someFunction()
  someOtherFunction()

If you would have written

  var xTask = SomeFunctionAsync();
  await WaitForSomeOtherFunctionAsync();
  string x = await xTask;

then it would be:

  try (var scope = StructuredTaskScope.open()) {   // JDK 24+ preview feature 
      var x = scope.fork(() -> someFunction());
      scope.fork(() -> waitForSomeOtherFunction());
      scope.join();
      String result = x.get(); // already completed 
  }

await implies async functions.

Looks like Java's "there is no simpler syntax than plain old synchronous code" is just a lot of extra manual wrangling of stuff


And the first two lines are an async function as they are, without any special handling.

They won't block the thread, and you can have millions of them.

Like it's no accident that c# with their goldfish attention span wanted to ship virtual threads as well next to all their millions of features.


Strangely enough virtually no materials on the internet show that. Everything is Executors, and Futures, and joins etc.

Concurrency != parallelism

I hope you know and understand the difference. Then strangely enough all my examples will make sense.




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

Search: