Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Love/Hate/Miss list for C# #1

Open
LPeter1997 opened this issue Oct 14, 2021 · 3 comments
Open

Love/Hate/Miss list for C# #1

LPeter1997 opened this issue Oct 14, 2021 · 3 comments
Labels
Wish List This one is yet to be split into single ideas

Comments

@LPeter1997
Copy link
Member

LPeter1997 commented Oct 14, 2021

As an initial step, I thought a love/hate/wish list of C# would not be too bad. Plenty of subjectivity here, but oh well. Love/hate should not be taken too seriously, it could be called like/dislike, even nitpicks went into the hate category.

Love in C#

  • Reflection: The ability to inspect types makes writing serializers and other inspection tools super easy. And .NET has one of the most powerful reflection systems I've seen so far.
  • Good BCL: .NET languages ship with batteries included. It's not so lacking as a C++ standard library.
  • No header files: Mainly coming from the C and C++ world, it's a breath of fresh air not having to keep up 2 files for a module.
  • Source Generators: They make metaprogramming much more powerful than in C++. We can generate complex constructs from simple, declarative notations, all compile-time.
  • Inheritance-based type/pattern matching: In most languages discriminated unions are implemented as tagged unions, which is fine, as long as you don't want inheritance. Once you introduce some kind of subtyping, the two features become somewhat redundant.
  • Amazing tooling support: This is a huge one. VS and Rider are extremely helpful. Great static analysis, suggestions, code-fixes, refactorings. You can include extra analyzers just as you would include a package, not even mentioning having an API to implement custom analyzers.
  • Switch expression: After the dreadful switch statement, the switch expression is a breath of fresh air.

Hate in C#

  • Lots of legacy quirks that makes the language feel kind of dirty at some places. And they are not going away because of backwards compatibility. Some of these are:
    • Covariant arrays
    • 0 literal implicitly converts to other integral types
    • Query-based LINQ syntax
  • The amount of new constructs is getting way out of hand and it seems like adding new thing to fix old ones is becoming the trend (which really didn't work out for C++).
    • An entirely new syntax for classes (records) that add equality, hash and print. Why not add the placement-syntax to classes and allow classes to auto-implement the equality and hash? Both derive constructs and tag types exist for this and I can't believe it would have been too much work for the compiler. The new record structs will make this even worse.
    • There is readonly struct but no readonly class. These modifiers seem to fly all around randomly, applicable to some things, while not to others, also making it very inconsistent.
    • Properties are completely separated from methods for some reason.
    • If the closed hierarchies proposal hits the scene, sealed and closed will enforce the same thing but on different scopes, making the feature redundant.
  • No variadic generics: Things like a type-safe OneOf for an arbitrary amount of types is kind of doomed.
  • No const generics: Implementing an N dimensional vector or an NxM matrix means either letting the dimensions be dynamic in a runtime value - type-unsafe - or writing/generating each case - not flexible, cumbersome.
  • Interfaces really only prescribe member-level constraints: There is no way to enforce implementing an addition operator for a type, or even adding type as a required member. This often means externalizing factory functions into factory objects, using reflection, or having some janky post-construction initializer.
  • There are plenty of problems with Source Generators:
    • They can't modify existing code: As much as I love the power SGs bring to the table, not allowing modification is a major drawback. They can't for example rewrite LINQ for you or act as proper decorators. They also make you litter partial all around. There is also no plan to remove this constraint, because they pose a security concern in the way they were implemented.
    • They work on strings: This is a really-really odd solution and the experience is very poor, compared to systems like Rusts quote.
    • Source Generators and dependencies suck together. Having a Source Generator include either a generation-time or runtime dependency is insanity. All because they decided to make it piggyback on the existing analyzers framework. Look at the hoops you need to jump for just a single case. Want to include a local project? Forget it, just include the sources recursively.
    • Because of all this, a nice auto-implementation for INPC without a weaver is still unlikely.
  • There is plenty of crud when defining a type:
    • Namespaces waste indentation (C# 10 resolves this)
    • When having to implement Equals/GetHashCode it's painful and there's crud that needs to be done but the compiler won't do it for you (like overriding bool object.Equals(object? other)). Admittedly, this is not something that has to be done but I've never written code where it is not desired, hence this is likely a holdback for a 0.1% case or something.
    • Similar pattern is when I implement IComparable and I want to also have IEquatable (which is common), again there is boilerplate. Again, this is not desired for partial ordering, but most cases are not partial ordering.
  • There is covariance for overrides of base elements, but none for interface implementations (AFAIK this is getting resolved partially).
  • Default interface implementations are really bad. They almost feel like they gave us some compositional elements or traits, where you can externally define behavior, but no, only when you reference the type as the interface.
  • The new keyword feels like it was just brought in because Java had it. Why not just type out the constructor name?
  • There is no type-parameter inference, resulting in things like the snippet below, despite having both var and new() for inference. Imagine only having to type new Dictionary<_, _>() (or something even shorter) there.
public IDictionary<string, int> MyDict { get; } = new Dictionary<string, int>();
  • Implementing the dispose pattern is horrific and there is no way to enforce disposal.
  • The switch statement. It's horrible. I usually avoid it at all cost.

Miss from C#

  • Closed type hierarchies, which would bring in DUs without the warning or useless default case in switch-cases.
  • A derive macro, which would auto-implement the common patterns correctly, in place. Records could just become classes that derive equality and debug-print or something.
  • User-defined, AST-based decorators. Instead of SGs that can act anywhere as long as it's new code, it could go the other way: only let the decorator act on the code it annotates, but allow to modify it. This could be a compile-time function, very much like Rust procedural macros.
  • A better construct instead of interfaces (traits or mixins):
    • Nonmember constraints (static and type constraints), which are also usable as generic constraints.
    • Proper default implementations, maybe unchangeable ones. For example, you'd only have to implement T1+T2, and T2+T1 would be automatically implemented, and there would be no way to override that. (just an example, not necessary in this exact case)
    • Externally implementable, maybe as long as the implementer type or the implemented trait definition is owned by the user.
  • Higher-kinded-types: I'm really not sure or sold on this yet, maybe type fields in traits would resolve most needs and this feature wouldn't be justified anymore. Rust only has type fields in traits and still manages to provide a fully typed LINQ-like API.
  • Way better type inference, mainly for generic parameters and constructors.
  • Constant computations: Since SGs require compile-time code execution anyway, compile-time evaluating some expressions would really benefit at some places. Maybe speed up serialization of known types, allows a wider range of arguments for attributes to pass, ...
@Happypig375
Copy link

Switch expression: After the dreadful switch statement, the switch expression is a breath of fresh air.

The switch expression. It's horrible. I usually avoid it at all cost.

🦆

@LPeter1997
Copy link
Member Author

Switch expression: After the dreadful switch statement, the switch expression is a breath of fresh air.

The switch expression. It's horrible. I usually avoid it at all cost.

🦆

Yep, updated, meant switch-statement as the horrible construct :^)

@WhiteBlackGoose WhiteBlackGoose added the Wish List This one is yet to be split into single ideas label Feb 1, 2022
@Kuinox
Copy link
Member

Kuinox commented Jul 18, 2022

Hate in C#

Attributes syntax. @Attribute(parameter) feel so much nicer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Wish List This one is yet to be split into single ideas
Projects
None yet
Development

No branches or pull requests

4 participants