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

I have used it since 2001, though Friday (and continuing next week and for the forseaable future). My biggest problems are:

* OOP is celebrated in the ecosystem. It's time to face the truth: the mainstream (i.e. not smalltalk) implementation of OOP is fundamentally flawed. I have done some pretty fancy stuff with aspnetcore and, OH BOY, does it take a while to unravel and understand that decoupled OOP wet dream.

* Nobody does the high performance stuff in the real world. DDD (which has several valid merits) has many idioms in netcore that are fundamentally incompatible with writing high performance code. You might be able to dilute DDD to achieve tighter code, but if you value explicit clarity above all else then you have limits. Modern procedural languages suffer from fewer opinions when approaching the best of both worlds.

Really this is a rant about OOP and, disagree with me about OOP being broken or not, OOP and OOP idioms are deeply entrenched in netcore (F# included).



A few points that may make your life easier:

- In later versions of .NET, struct enregistrering is very powerful, it promotes struct's contents to CPU registers. You can write high-performance and allocation-free implementations for value objects, monads and wrappers of all kinds without much issues nowadays.

- Guarded devirtualization helps a lot if you heavily rely on interfaces because it allows further inlining and reduces method calls cost.

- Unfortunately LINQ is no match to Rust iterators which get easily vectorized. However, there are low-allocations and simd-ified implementations which might help with your goals. See https://github.com/asc-community/HonkPerf.NET

After all, DDD requires special care when describing its domain definitions in code but you really don't have to go OOP route nowadays for the core logic of your applications. Also records and record structs make it very easy to define contracts and state on the go without having to go with tons of boilerplate I keep seeing in a very much DDD-oriented project my team is responsible for.


Do you have any links to stuff about struct enregistering and guarded devirtualization? I'm familiar with the concepts and have gotten .NET to do the latter intermittently but if there's documentation on how to make them happen reliably that'd be great to see.


Structs will be stored in the registers of the CPU if the compiler thinks there is enough space.

As for devirtualization: https://www.infoq.com/news/2017/12/Devirtualization/

https://devblogs.microsoft.com/dotnet/performance-improvemen... - you have to go to the section where he talks about devirtualization.


> without having to go with tons of boilerplate I keep seeing in a very much DDD-oriented project my team is responsible for.

And that's the unavoidable reality. My team are extremely open-minded, but I have no idea how to bring up this subject with them.


I fancy doing the domain modeling in F# since it lends well to that and the business logic in C#.

https://fsharpforfunandprofit.com/ddd/


It's not like MS wants to make it easy to use a functional or procedural paradigm.

I tried to bind an IConfiguration section to a record but I couldn't since in condensed form a record lacks a default constructor. So I had to write a class like record and add Init to each property to make them immutable.


I think you can add default constructor manually. Something like this:

  public record Foo(int X, int Y)
  {
      public Foo():this(0,0) { }
  }
Or make all parameters optional

  public record Foo(int X = 0, int Y = 0);


Tried with

public Record Foo(int x = default, int y = default);

and it still complained about missing a constructor. I didn't try to add an constructor like you suggested, it's still shorter than defining all members.


I think this is a valid critique. But then, most of the development (C aside) has been a victim to OOP.

I have begun to think that OOP creates more problems than it solves. Added complexity and performance loss being the biggest. It's insane if you peek into a big projects where many developers came and went and each of them read an article about what it seemed an interesting design pattern and tried to implement them.

You can have some kind of procedural programming if you use classes just to store data and use static methods. But the frameworks are OOP and there is no way around that unless you write your own frameworks.

I am a big fan of Mike Acton's Data Oriented Architecture speeches, but I didn't find a way to implement something similar in C#.

For those interested in something better than OOP, here's a few links:

https://youtu.be/rX0ItVEVjHc

https://youtu.be/QM1iUe6IofM

https://youtu.be/pgoetgxecw8


.NET now has a lot of functional stuff. It has a whole functional language (F#), that can natively interop with .NET libraries (even if they are written in C#)

C# itself has many functional constructs.

With the modern minimal APIs, you can create a single file web application servers, without ever creating any objects.

OOP is the bread and butter of .NET, just like Java.


> C# itself has many functional constructs.

-ish. You can't have true FP without a high level of referential-transparency, that's something the CLR doesn't support due to restrictions in the CLR's type-system, which in-turn restricts languages on-top of the CLR like C# and F# (and we see that in the limitations in the F# language).

Back to C# though: Function-references are passed as strongly-typed delegate objects (which include a bound `this` reference), but there's no reification of function parameters: so there's no spread-operator for parameters, nor currying, nor equivalence of delegate-types. There are workarounds for most (but not all) of these issues, but it just results in the tedious writing of wrappers everywhere, which drives me mad.

A trend with C# that I've noticed is that it gets a lot of cool features, but each cool new feature always has a wart on it somewhere.

* User-defined implicit type conversion, but only between user-defined types, not between interfaces, and there's no in-box interface that expresses such a conversion for use as a generic constraint (i.e. we still need an `IConvertTo<T>`).

* If you use interpolated strings you're forced to use CurrentCulture. You need to jump through a lot of hoops to use InvariantCulture or your own interpolation formatting logic (improved in C# 10, but still doesn't address the jump-through-hoops problem).

* Generic type covariance/contravariance, but only for interface types, and when the type-arguments are reference-types.

* Generic type constraints, except they don't participate in overload resolution.

And so on...


I left OOP when I left Ruby back in 2015. I won't be going back to OOP ever. I use Elixir now.

How much C# is functional these days? I know linq had the beginnings of it waaaay back in 2010 but do you see a shift in more c# devs going functional instead of OOP? Or is it just another JavaEnterpriseFactoryFactory.cs


If you're curious, a small repo that highlights some of the functional features in C# and compares it to JavaScript and TypeScript: https://devblogs.microsoft.com/dotnet/announcing-entity-fram...

One of the best functional features in C# is probably pattern matching: https://timdeschryver.dev/blog/pattern-matching-examples-in-...


OO and functional are orthogonal, and C# has both. Definitely more of the former, but it's catching up on the latter - records, immutability, pattern matching etc.


In .Net the F# language is specifically targeted at functional programming. If thats your interest, you're probably better checking that out over what C# supports.

https://docs.microsoft.com/en-us/dotnet/fsharp/what-is-fshar...


>I left OOP when I left Ruby back in 2015. I won't be going back to OOP ever. I use Elixir now.

Lucky you! But most of us, poor folks, are getting paid to write software in an OOP language. The huge majority of the most used programming laguages, C aside, are OOP.

>How much C# is functional these days? I know linq had the beginnings of it waaaay back in 2010 but do you see a shift in more c# devs going functional instead of OOP? Or is it just another JavaEnterpriseFactoryFactory.cs

You can write pretty functional code with LINQ, pattern matching, records. Discriminated unions will be also added in the future.

In the last two projects I was working on, people mostly uses procedural programming. Where you could find some design patterns it was mostly juniors wanting to show off.


They added record classes (amd record structs) a year or two ago. Discriminated unions are one of the top requested features and they keep hinting that they might make it into the next version but it keeps getting pushed back.


They want to implement discriminated unions on the heap. I'm excited for the code clarity, but it falls very short of the performance concern raised by an ancestor comment. That's the OOP trade-off.

We're also stuck with structured exception handling, so we'll have none of that Rust Result<T>, or Go tuple, goodness.


>They want to implement discriminated unions on the heap. I'm excited for the code clarity, but it falls very short of the performance concern raised by an ancestor comment. That's the OOP trade-off.

That isn't also the case with Haskell, OCaml and F#?

IMO, if you care about performance you go procedural and watch how your data is aligned and how many allocations you do. Functional programming kind of requires to use immutable data structures, so you end up making copies and that will impact performance.

I don't think you can have both highly performant and expressive code.


C# has tuples that are stack allocated


F# allows you to put DUs on the stack, with `[<Stack>]`, for what it's worth.


(By this I meant `[<Struct>]`, of course.)


If you look at great C# (e.g. aspnetcore source), you'll see that it's heavily OOP - that is definitely the idiomatic approach.

I think the main problem with functional .net it that all function types are nominal. Functional C# can get really verbose because of that.


C# is fundamentally OO, so while you can write some pretty functional code in C#, you can't get away without touching OO.


If you call libraries and frameworks, than yes. But you can use only static classes as code modules with static methods and only use regular classes for data.

If you don't make use of encapsulation, inheritance and polymorphism, there isn't much OOP. Nobody forces you to use builders, factories, singletons and what not.


How, in your opinion, does “mainstream” OOP differ from what Smalltalk OOP was/is?

I tell people Alan Kay’s 97 OOPSLA keynote “I invented the term object oriented, and this is not what I had in mind”, but I find it difficult to articulate to people who are doing “mainstream” OOP, what they’re missing. It’s like trying to explain the taste of salt.

Just curious if you have any better way of explaining it.

Oddly, I’ve been learning Elixir/Erlang for the last year, and though it’s definitely not OOP, there’s something oddly resonant about it with my Smalltalk past from 10 years ago.


Agents with explicit messages are much closer to Smalltalk OO than virtual methods implemented via dispatch tables, with statically typed arguments.

OOP has its place. I think it's decent in the small - when you're dealing with data structures which inherently have state and need to maintain invariants - and it's decent, in the agent form, in the large, where you have encapsulated state in separate agents which communicate with one another using messages, as long as you have other actor-model things like fault tolerance.

It's in the middle, when you're trying to write a big app filled with communicating mutable objects with lots of mutual references, that things can get pretty sticky. My experience is that it's better to favour complete knowledge of concrete types than to minimize knowledge of concrete types and leave them open to extension. That is, pattern matching is generally preferred to polymorphism and it's better to get a compilation error when you add a new subtype than it is to try and always squish your new subtype into a polymorphic interface straitjacket to use virtual dispatch instead.

There are other problems with object orientation, particularly the way it produces very pointerful code and isn't friendly to - doesn't take advantage of - high latency high bandwidth memory.


> Alan Kay’s 97 OOPSLA keynote

Is it this one?

https://www.youtube.com/watch?v=oKg1hTOQXoY

Edit: I suspect it is this one, at 10:33 Alan says "Actually I made up the term <<object-oriented>>, and I can tell you I did not have C++ in mind."


Alan Kay has been redefining what he calls OO for decades. He has lost all credibility.

He cribbed OO for Smalltalk from Simula, same as Stroustrup did for C++. (Smalltalk-72 was not OO.) "OO" is just a name for something that already existed.

Anyway there is nothing very meritorious about OO: it is sometimes the right thing for part of a problem. But organizing a whole language around it is as idiotic as organizing a car design around making left turns.


> OH BOY, does it take a while to unravel and understand that decoupled OOP wet dream.

If you are referring to ASP.NET then I think you might be describing DI and IoC more than OOP in general.

The evolution of ASP.NET has definitely be one of embracing DI and IoC design patterns.

With ASP.Net Core the conversion is complete as the foundations of that framework are built on DI and IoC design principles.

However, the trouble with DI for newcomers is it can be very hard to understanding what's going, only because of much of the action happens in code that is not visible.

It also means anyone using the framework really has little choice but to also adopt DI and IoC design principles, otherwise they'll just find themselves fighting the framework.


Yeah I think you're right it may be the DI and IoC .. I've seen a lot of complaints about that. Usually that it's not 'simple enough'.

But .NET is not a beginners coding environment, it is instead designed to support being all things to all people. That includes including high assurance, and high performance. The DI and IoC is there to support high assurance.

.NET is also quite reasonably approachable for beginners, but as a tool with advanced capabilities too there is of course a bit more of learning curve, and if the reasons why the IoC are there are explained earlier on it helps people to understand that it is designed very sensibly, just with a bit more scope than beginner oriented platforms.


In ASP.NET's case I think DI is useful for the way the framework is structured. It just makes it easier to register services using DI container and call the code in controllers and other services.

You can, how ever, to not use DI at all. Write the business logic in controllers. Write business logic in static classes. Write business logic in regular classes but instead of registering in DI container you instantiate them yourself.


The irony is that if you look at a framework like Nest.js, it's pretty much a reaction to how difficult it is to manage and scale pure express/Node projects without more structure and higher order patterns like DI and IoC.


“Nobody does the high performance stuff in the real world”

We run an high performance, allocation free automated trading platform on .NET. I guess we are nobody…?


Interested in hiring a new team member?


>the mainstream (i.e. not smalltalk) implementation of OOP is fundamentally flawed

Any nice readings you could link me about this view? Thank you!


Here's a really popular video that touches on many of the contentions: https://youtu.be/QM1iUe6IofM

My personal tipping point was the realization that OOP forces you to first figure out how you solution maps to OOP, before figuring out (or iterating on) how it maps to code. It creates nothing more than accidental complexity: https://emangini.com/2021/07/05-accidental-complexity/


Its hard to know what people mean when they say OOP anymore.

Im just watching now and he advocating for your code to be "procedural" rather than "object-oriented".

And when he says "procedural", he doesnt specificlly mean "functional programming". So, when he says "imperative procedural programming" he means that you have no explicit association between your datatypes and your functions/behavours.

And just to be clear they are not saying its a problem to see an object in your code!! Its the traditional OOO (Object Oriented Objects) that have state and method calls/behaviours.

And thats pretty much the way I code in C#. My data objects are just that. Simple data! And I have other objects that are a collection of functions that take in POD (Plain Old Data Types) objects or return PODs.

He goes on to explain how encapsulation is the problem. Thats where you have an object that holds state/information hidden behind a public interface as well as methods to mutate the state. And when you call that method, it might call through to many other methods on other classes. And if thats the case, it means the object your calling the method on has references to those other classes it depends on. Which means those dependencies/objects must be provided to it via construction.

I have not watched the whole video but I tend to agree with what he is advocating.






Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: