This somehow reminds me of an old gamedev trick when at the start of the development cycle you'd declare a huge static array somewhere in the code to "reserve" some memory. You'd then "rediscover" and reclaim it a year later when the game needs to ship but no longer can fit in a memory.
That is, any sort of astonishing improvements is often a sign of the original version being crappy, so it's also a red flag even if it's ultimately the good news.
> That is, any sort of astonishing improvements is often a sign of the original version being crappy, so it's also a red flag even if it's ultimately the good news.
How is it a red flag? I don't quite understand your comment.
> assuming that one of the most widely used runtimes in the world had easy optimizations to do, about 20 years after it was first launched.
Perhaps not trivial, but it did have easy optimizations - within hours of it being open sourced, external people who had not worked on the codebase were making pull requests with straightforward (and in some cases substantial) optimizations. Matt Warren (author of Benchmark.NET) has tracked many of these.
Yeah, but those happen in any code base. You can always find improvements, sometimes changes that seem trivial in hindsight. That doesn't make the original code base crap :-)
Yup - absolutely agree. Being open source and accepting contributions means that people can spend time on bits which otherwise will not get prioritised.
When Microsoft worked on the .net framework. Performance was not a metric they used. The only performance goal they had was basically “it can’t be slower than it is now”. So performance can’t regress.
Since open sourcing performance is one of the goals in mind. And it shows.
I think the author of the comment is saying that existence of low hanging fruits is evidence that people haven't gone out to pick fruit in a while. Where picking fruit really means "improving quality", and low hanging fruit means "quality issues".
I don't think I agree with this, but hopefully that makes it more clear what was meant.
Having followed the process and lots of improvements in .NET over the past year or so, there's not that many low-hanging fruit anymore and definitely not the kind where a quick 5-minute change brings considerable gains. Some of the things that have happened are reducing allocations aggressively, which _can_ a quick change by using newer language features where applicable, but a lot also have been things like vectorizing algorithms or adding additional optimizations either in the JIT or in other code that's generated (regular expressions). Those are rarely trivial.
It's more like there's been a few hundred small individual improvements and those provide a lot of improvement in total.
Your comment somehow reminds me how about 20 years ago me with my friend tried to came up with a stupidest possible conspiracy theory ever, and what we settled on was an idea that all possible hardware features are already implemented, and when you install a new CPU for example, it's actually just a heating element and a collection of jumpers to tell motherboard to adjust a performance of the real CPU hidden in it accordingly.
Funnily enough that seems very similar to "cpu binning" in which sections of a cpu are disabled based on stress testing so you end up with different models but the same underlaying hardware.
this wasn't that far off. for a while you could upgrade celeron to pentium soldering a connection, the r5 are r7 with cores disabled, the athlon II did the same but was easier to unlock as the extra core are turned off not from yeld but to follow market demand.
Well, your conspiracy was somewhat true for AMD PHENOM X2 550, a 2-core cpu which was the same as the 4-core model, but had 2 cores deactivated. Users were able to unlock the cores with BIOS tricks (and some future mobos would do it automatically though it depended on the quality of the chip. AMD even played along and released an optimized for overclocking version (since users had to bump voltage to unlock the cores in the first place)).
But also the user you are quoting, the "gamedev trick" is actually somewhat of a true story as well. But it wasn't an optimization trick. It was just different teams that each acted as if they are working on the most important thing, competing for limited old-hardware resources! Some coders, pre-version control, would just pre-allocate ram even if they didn't really need it, just so they don't have to go through the dance of asking other teams to release some. As you can imagine, when the project ended up going beyond the hardware limits causing chaos and stress to the devs, those people would descend as heroes and say "Here I was able to free 512kb!" (from their unnecessary preallocation calls)
I've heard the same conspiracy theory doing the rounds, that CPUs can in fact be much faster already than they really are (like, generations ahead), but because They want to make money they're intentionally slowing things down with incremental releases.
Joke's on them, software gets slower faster than hardware gets faster.
> That is, any sort of astonishing improvements is often a sign of the original version being crappy, so it's also a red flag even if it's ultimately the good news.
That's a question that can't be answered by comparing one version of .Net to another version. Instead we'd need to compare the performance of .Net to a rival system like the JVM. edit Or rather, a JVM.
In this case, it's rather that consistent work toward transformative goals has unlocked optimisations that have been deliberately and consciously been worked towards for the last several years and versions.
> That is, any sort of astonishing improvements is often a sign of the original version being crappy, so it's also a red flag even if it's ultimately the good news.
Actually, I'd argue they are fixing other people's mistakes with some of this.
A great example are the changes around devirtualization and/or removing certain branches, be it in the code level or at the CLR (.NET VM) level. 5-10 years ago, these changes would likely provide far less benefit, possibly not even worth the effort.
But thanks to Spectre/Meltdown/etc, getting rid of such things (especially near the IO layer) has become more important.
It’s been a hot minute since I wrote any C# (pre .NET Core). I kicked the tires yesterday. I have to say, documentation is all over the map, and the old DI / enterprisey boilerplate is still alive and well. Coming from a Node + TypeScript, Ruby, and Go world, C# still feels like a lot of ceremony. So, I gave F# a spin again. It’s always had a special place in my heart. But while the language is fantastic, the tooling (on OS X) and documentation made it difficult to get off the ground.
So, what’s the best-organized resource for getting up to speed on .NET these days?
Regards ceremony and DI: I think the templates are more Ruby on Rails style while the framework can be trimmed down to nodejs + express simplicity. This sample here is node+express like equivalent (thanks to C# 9.0 top level statements, there is not even a Program class and a Main entry point)
WebHost.CreateDefaultBuilder().Configure(app =>
{
app.UseRouting();
app.UseEndpoints(e =>
{
e.MapGet("/", c => c.Response.WriteAsync("Hello world!"));
e.MapGet("hello/{name}", c => c.Response.WriteAsync($"Hello, {c.Request.RouteValues["name"]}"));
});
}).Build().Run();
I also stumble over that once in a while but I have no hard feeling on it.
Regards Documentation: As a fanboy this is hard to say: While there is documentation for everything, the split between .NET Framework 4.x and .NET Core/.NET 5+ is a tough thing.
However, especially in the HTTP land, the namespace give away instantly on any sample (System.Web => .NET Framework vs. Microsoft.AspNetCore => .NET Core/.NET 5+).
To speed yourself up when you have previous experience: I think the docs.microsoft.com page is not too bad on that.
- runs on x86, x64, arm32, arm64
- runs on Linux
- RedHat adding RedHat specific networking adapters
- runs in Docker
- supports systemd as service manager
- is faster than anything else
- stateless is implementor's choice and easily doable
- Redis, memcached, postgres, mysql, ... driver
- Works on AWS and Azure on Linux OS
- VS Code, Rider (and VS on macOS) and dotnet templates for development
I'll need tutorials about starting a new web app, running it in dev mode without ide, developing with stateless architecture and deploying it. There is a wealth of resources about developing stateful web apps in Windows+VS and deploying it to IIS but learning .net from scratch outside this norm is a huge challenge.
> I have to say, documentation is all over the map, and the old DI / enterprisey boilerplate is still alive and well.
First of .NET has excellent documentation, right up there with the PHP. Still my favorite documentation site to snowball through sometimes.
Second, "old DI/enterprisey boilerplate" is what saves you when you get handed the project 5 years down the road, and have to be productive as fast as possible.
Edit:
> So, what’s the best-organized resource for getting up to speed on .NET these days?
> First of .NET has excellent documentation, right up there with the PHP
.NET had excellent documentation. I still pine for the truly-comprehensive MSDN of yore (where "yore" == 2007 or so). Back then, they had less ground to cover (only one .NET Framework and it ran on Windows only), and a bigger budget to do it.
These days, and especially after the big migration to a new CMS a few years back, it's really hit-or-miss for me, especially when it comes to UI framework docs.
Absolutely. Visual studio is a pile of garbage in comparison and it probably reflects negatively on .NET as a platform.
They're noticeably working on making it less terribe lately, but at their current speed and considering the work ahead it's going to take multiple years until they have anything anywhere close to Rider today while JetBrains are developing it at multiple times the speed.
Drawbacks: No XAML hot reloading in WPF, lacks GUIs for Microsoft's questionable file formats (resx). That's it.
Ive been using VSCode with the C# extension whenever I can, and it has been worlds better than Visual Studio. The only thing it has issues with is multi-solution projects.
I should say, as someone who wrote Java/Kotlin for years on IntelliJ, Rider is a welcome addition to the C# world for tooling. JetBrains really does try to produce the best editors they can.
I can't comment on Rider specifically, but after 10 years of C# on VS Studio (no Resharper), I was sorely disappointed by Android studio's "intellisense" (same base as Rider afaik).
I should mention I use Dart / Flutter, this might not be a problem on Java or Kotlin.
If I don't use the right case it's unable to find the right keyword and most of the time doesn't offer the most relevant option after 'myObject.'
The other day I was joking that my main mode of programming on Visual Studio is pressing CTRL+Space and let the IDE code for me. I can't say the same thing for Android Studio.
That’s strange, though probably due to dart being a small language. For java and kotlin intellij absolutely blows visual studio out of the water and you really can program with only pressing ctrl+space
It's not the same "intellisense". The UI shell is the same, but Rider's autocompletion is provided by Resharper running out of process.
I don't know what Android Studio uses for Dart, but it's definitely not that! It's definitely not an issue for Java, Kotlin, Go, or even languages with more complex semantics like Rust.
> Match case - Select if you want letter case to be taken into account for completion suggestions. Choose whether you want to match case for the first letter or for all letters.
Also, there are a few interesting plugins that improve the autocomplete in IntelliJ further, such as Codota: https://www.codota.com (language support varies)
It's been a while since I've used Ruby for anything serious, but I think most people still use https://ruby-doc.org/ for the official docs. What you linked is the site generated by rdoc, which has never looked very good.
I don't really feel like there is that much more boilerplate with C# than there is with TypeScript (although I do like TypeScripts's ctor shorthand that creates members for you from ctor params).
C# 9 also brings top-level programs, which essentially eliminate all ceremony for the entrypoint[0] - an app can now be as simple as:
I haven't used C# 9 yet, but I write a lot of console apps and services/daemons using .NET Core (the world is bigger than web apps :), but I don't see why you can't use top-level programs for ASP.NET Core apps, as they have an entrypoint too.
You would of course still have all the usual ASP.NET configuration and DI setup to do though, so the impact would be rather limited.
My understanding of this feature is that it's to encourage beginners to write code that does something without first having to learn about namespaces and the entry point concept. I expect most commercial apps and open-source projects will continue to use namespaces and an entry point method in their code.
Exactly! And on top of that I've mentioned in many conversations how teaching Java and C# as a first language often confuses people into believing that programming is about syntax rather than logic, because they spend so much time searching for syntax errors in their first hello world application.
For hello world with 29 tokens if i count correctly for C# vs 4 tokens for a Python program, guess where beginners have an easier time to fail?
Don't you guys think that entrypoint is only 0.0000...1% of your development time? Not having to write Namespace, class, main method (which comes in template anyway), is not something I would use to compare languages.
Its nice for beginners, not having to explain every weird word they see, but not much for professional development.
However managing complexity with various language features all over the project makes impact.
.net core web is pretty much a mess. The very sight of dependency injection configuration is cringe. Even having to make the decision to go tables first or code first is a burden. I'm surprised at myself for saying this but given the chance I'd much prefer spring boot.
Coming from a different perspective: Once you've been using Entity Framework for a while (I assume the OP is referring to EF, since there tends to be a 'Use what microsoft ships' culture in the .NET community), is that the APIs available to a "Code First" approach vs. a "Model First" approach are different. If you use Code First and Code Migrations, you don't have access (without getting out of Code First entirely!) to creating Views; and in EF 6 at least, creating indexes was a chore as well. I believe this has been fixed in EF Core; but regardless the mainpoint still stands: The APIs are different and what is natively available is different. At some point doing "Code First", you'll still need to write raw SQL, and that's unexpected if you are planning on doing your work in C# only.
Regarding Dependency Injection: It's very much suited towards enterprise web applications; and its failure mode is trying to get DI to work seamlessly in a non-web application sense (C# is a general language; and .NET supports console, GUI, and web applications) and once you get out of the we application thread-per-request pipeline, you find yourself hitting rough patches.
I believe the smart advice says don’t go code first if you already know what you’re doing with SQL but it’s definitely sad that there’s such a huge barrier between these modes.
As for DI it can definitely be used fruitfully outside of a web application, I suggest the book Dependency Injection: Principles, Practices, and Patterns if you’re curious. It covers examples from ASP.NET Core MVC to console apps to GUI.
I've iterated on the style of my own development over time, and I hadn't really stepped back to look at where I was now for a while. Interestingly enough, I find that I've "worked around" both of the things you mentioned in one way or another.
I use EF, but generally only for migrations, and use Dapper for all the actual access. This avoids a lot of the headache I've had in the past with EF tracking, lazy loading, and foreign keys. I also avoid using views for anything, even at work where I can guarantee with many 9's worth of certainty that I will always be using MSSQL. Part of this is because EF doesn't support it for Code-First, but part of it is also generally trying to be proactive rather than reactive and pre-generate anything I'd be using a view for. Being able to more easily switch providers and move to a different RDBMS is just a "perk" that I'll likely never take advantage of.
I despise the Microsoft DI system. I won't bother going into details, it just doesn't seem to work "right" (that is, the way I want or expect it to). On the other hand, I've used Autofac for years and just seem to "get" it, so I sometimes go to relatively great lengths to use it, even when the Microsoft version would have been a lot easier to get up and running.
You aren't forced to use it. It's strange that a single library would make you avoid the entire ecosystem, even though it's one of the most advanced ORMs available.
Any documentation that doesn't have to do with EF has nothing about EF. You can use whatever data access code you want. There are thousands of examples of Dapper, for example.
"Dependency injection" frameworks look to me like a hack. In a more expressive language, such as F#, one could probably describe what needs to be done symbolically, and then write interpreters that implement the different behaviours required.
The entire web platform is a mess because of an unclear complain about configuration? You could at least try being specific if you’re going to make such stark claims.
It depends how you write TypeScript. It's explicitly designed so that you can write it like C# if you want to (classes, interfaces, enums, dependency injection), but you can just as easily compose plain data types and write plain first-class functions and never touch classes.
I’m a Dev doing mostly C# and JS. I’ve heard some of the complaints on HN mentioning DI and really don’t get it. I honestly feel like it makes my code cleaner and more testable. For example, I don’t have to care where my IFooService comes from or how best to instantiate and dispose, I just request one in the constructor. For a framework to be flexible, you need _some_ plugin capability. Asp.net seems to do pretty good at giving you nice defaults but letting you swap out with DI. All my dependencies are laid out nicely in one place (Startup.cs) and then used throughout. DI gives you that and I’m glad that it’s baked in now as opposed to having to pick a 3rd party. Maybe I’m biased, though. Help me understand why NOT to use DI? Is it just moving people’s cheese or are there other reasons?
NodeJS has had various libraries to mock dependencies for a long time. So for me, I've always seen it as an extra layer on top of something that was already possible.
Do use DI, it's great. It helps with structuring into entity/model/service/controller and treats DI providers as a global resource. But don't require configuration and instantiate lazily as a class member like a sane framework instead of having to pass it to constructors.
> In .NET, the usual convention is that you make required things constructor arguments, so that there's no room for confusion.
Except in Windows Forms, where the norm is a zero-argument constructor and setting everything via properties, because that approach works with component-based design-time tools. Come to think of it, I think WPF and its descendants do the same.
You can tell the designer how a particular type has to be created if it doesn't have a parameterless ctor, but yeah, it's generally easier to do it that way.
It's been a while since I touched either WinForms or WPF, but, if I recall correctly how things worked, I don't think this is actually an exception to the convention. Don't they also make everything optional? In which case, the correct way to follow the pattern would be to also leave the constructor parameterless and configure everything via properties.
Constructor DI is extremely important though, because time and time again lazily injected dependencies via properties results in circular dependencies and sometimes missing dependencies that fail at runtime. I'm not talking about simple cycles either, every time I have seen lazy dependency injection done has ended up in an A -> B -> C -> D -> A scenario.
Passing required dependencies by constructors makes this extremely apparent, not just to the DI framework but to the developers themselves. With constructor injection I can immediately grasp exactly what dependencies an object depends on without even going into the full code flow to look at all the code to find all the IOC container calls.
Edit: Also constructor injection is critical for testing dependency injection. IT allows you to have automated tests that construct every relevant object using the same DI setup as your live application and you can verify that the DI framework can resolve dependencies for everything.
If your DI is lazily done this cannot happen unless you explicitly call all code flows, opening yourself up to runtime errors that may be missed. Unit tests won't catch this because the dependencies of your production site will be different than the dependencies of your test scenarios.
I'd go a step further and say that if calls to the IoC container are scattered all around your code, you're not actually doing DI. You're following the service locator antipattern.
We are currently on a long term process of migrating a large C++ codebase from a service locator model to just passing dependencies through constructors. New code is significantly simpler to both use, understand and test.
Dependency injection is just using passing your dependencies as interfaces, especially in the constructor. Nothing more. Dependency injection frameworks are DI combined with a service locator.
Dependency Injection Frameworks have the same issues:
- understanding what is going on is harder than injecting objects by hand.
- you have limited control over object lifetimes (when objects are created and disposed). Permanent, scoped and transient might be enough most of the time for a web service, but as soon as you fire off tasks that can outlive the requests or when you use it in different circumstances like UI it’s getting a problem.
The core of a DI container is generally extremely simple — objects are constructed by scanning collections of registered dependency types for matches and constructing them with the same process before passing them on. It has a recursive structure that I would even call elegant.
C# 9.0 records with init only setters is a large step to solving this issue. Still has security concerns with injection into serialized objects...it's a difficult problem to solve.
Going off of the dot net core 5 release thread, I assume it’s because of misunderstanding or lack of familiarity with the platform. There was a very confused top level comment that seemed to be complaining about DI and dependency drilling down several layers. That’s just not using DI at all. In general DI is a great pattern that encourages lose coupling and testability, so I also don’t get the complaint.
If you're talking about my comment, you couldn't be further from the truth.
I've actually worked with C# for over 15 years now and written apps with (in order of age) VBScript, webforms, web services (the MS SOAP stuff), MVC 1, MVC 2, MVC 3, MVC 4, Web API, Web API 2, the OData one I forget the name of and asp.net core. Oh and Silverlight. I've probably missed something. On yeah, WCF. And even WWF! Basically EVERYTHING web-related to C#. Plus enough work in PHP, Python, Ruby, Express, etc. to be able to compare different approaches. And even sometimes VB.net, I've maintained and then migrated entire code-bases from that to C#.
I'm not entirely sure what more familiarity I need?
I'd also note that there are a lot of other people agreeing with my in that thread, almost all of whom show a decent technical understanding. And it's got a lot of upvotes.
But, even if I "misunderstood" or have a "lack of familiarity", that speaks of how poor the design/documentation actually is. If you can so easily shoot yourself in the foot with it, it's not fit for purpose.
I don’t know if it was your comment, but you can tell me you’re the queen of Scotland for all I care. Just look at your complaint above in this thread about DI and how no one can make sense of it. If you have so much experience you hide it well.
Funny, I've just checked those comments and again, a bunch of people agreeing with me. Are you wearing your "I can only see comments that back my biases" glasses?
To me, it's clear that some people love DI. SOME. Use it if you want. I don't want to. Stop dictating what's "good" code to experienced developers.
Yeah, I can see the thread just fine, I can also see the majority of posters and the more substantive posters disagreeing with you. Also the only person who comes close to dictating anything here is you.
And I’m still not seeing the benefit of all that experience by the way.
None of that is familiarity with DI, which wasn't as prominent or included in the framework itself until .NET Core. Perhaps it's just not a fit for you.
Not the OP, but I never used .NET core, but used DI pretty heavily for most of my 15 years writing C#. It was there before .NET Core. Maybe you mean it's improved with .NET Core?
"wasn't as prominent or included in the framework itself"
There was no built-in DI container before, you had to use your own like Windsor/Autofac/Ninject/etc, and it wasn't as popular because of it as many people skipped it for smaller/less enterprise projects. I'd say exposing more people do it, even if the built-in DI is rather basic, helped improve many new projects that otherwise might not have chosen to do so.
Again, none of that means familiarity with DI. Why deflect about the language? This has nothing to do with C#.
You never answered what your actual issue with DI is (the concept, implementation, containers, etc), or what your alternative would be, even though numerous people have asked. That makes your complaint seem largely unfounded or a combination of confusion and lack of experience with DI.
If you don't want to write any unit tests, for which there are a myriad of reasons not to, whether you're prototyping, throwing together a small once and done for a client, etc.
Then it's just a massive waste of time and gets in the way.
I really feel that you (and Microsoft) have completely lost sight of the reason why DI was ever even necessary and have become blind to just how much extra code it generates. The only reason was to inject mocks, and people hand-wrung for ages over it because the code to do so infects all the other code. Then people started to try and back justify it with IoC, but it's never really been a convincing argument.
It's also something that if you're not working regularly with it can be very mysterious and when it fails, fails at run-time, quite inscrutably sometimes.
From my perspective it's just boilerplate magic that if you actually need it, fair enough, but if you don't is a persistent, nagging, pain in the ass.
> If you don't want to write any unit tests, for which there are a myriad of reasons not to, whether you're prototyping, throwing together a small once and done for a client, etc.
Then it's just a massive waste of time and gets in the way.
A massive waste of time? Adding a constructor and one line to startup.cs is a massive waste of time? Just how long does it take you?
> It's also something that if you're not working regularly with it can be very mysterious and when it fails, fails at run-time, quite inscrutably sometimes.
If DI fails it’s usually just because you’ve forgotten to add that one line to the startup.cs, and the error you get will tell you that. Can you give me a concrete example of how DI is inscrutable?
"The only reason was to inject mocks". Must disagree...the reason for DI is to support the Dependency Inversion Principle : "High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces)." Won't even go into the host of side-benefits that are gained from adhering to the DIP.
In C# the idiomatic way to achieve this is via constructor injection, which is made much easier to manage in ASP.Net with a DI library that works with the framework for you (either the built in one or AutoFac / SimpleInjector, etc.). But you don't have to use a library; many times I'm writing a simple-ish console app and just use good old "Poor Man's DI" where I manually construct my object graphs at startup.
I'm not even a C# coder and I can't understand the complaints about this. If you want to code fast and fancy free, use Python or PHP. If you want to use enterprise patterns that will make your code clean, testable and nicely modular, then go for something like C# or Java. Why even use C# if you can't be bothered with DI because... too many lines? mindblown
Simply because not only does it not have to be like that, for years we weren't forced to use this stuff if we didn't want to.
I like C#, no, I adore C#. I have a bad memory for even the simplest method calls and the sheer power the intellisense in a statically typed language gives me to just not care means I can just code with joy.
My personal view on it has always been that someone in MS wrote MVC 1 in response to Rails on the sly. It was fantastic, a breath of fresh air into the MS constant misunderstanding of the web. Especially compared to webforms. For a while, everything was good. Then somehow 7 or 8 years ago the ASP.Net team got obsessed with ramming "best practices" down our throat and everything's gone a bit downhill from there.
You still are not forced to use it. The documentation will heavily emphasize it, but you can still manually new things up to your heart's content if you want.
This isn't even about C# at all, actually. This is about the web application framework ASP.NET Core.
And they don't even have a point in this regard, because nothing is forcing you to use the DI. You wanna make a database connection with ADO.NET and a static connection string in your Controller? Go ahead. You can do that. It's no more effort than it ever was.
You want to grow your app to run it in multiple environments and be confident you don't clobber your configuration during migrations? Use ASP.NET Core's DI.
Every app framework has some "magic convention over configuration". I personally think ASP.NET Core's "magic" is a lot less pervasive than in, say Django, or RoR. When I was first learning ASP.NET Core--coming from WebForms--there was a learning curve. You don't just throw everything into a Web.config XML bag of doom anymore, with a single, static configuration reading tool that reads just that one file. That particular magic has changed. I mean, if you want to do that, you can, just read the file yourself. But it's not wired up by default anymore. And the new way of doing things was easily learned with a couple of afternoons of reading the documentation.
Which you can do, because there is actually documentation, and a lot of it. I think part of the problem might be that people are used to other web app frameworks where the documentation is pretty lacking, so they don't even think to go looking for documentation on ASP.NET Core and think they can just jump in and figure it out. I don't think you could do that going from Django to RoR or vice versa, but people complain when they can't go from ASP.NET WebForms to ASP.NET Core without learning new ways of doing things.
I hope that's a typo because it's literally the opposite of what you're saying in #2.
It is precisely when you use a DI container that the problem is hidden, whether or not it's solved.
In most projects that use a DI container, the problem is usually not solved correctly, but the developers are oblivious and overconfident. They think, as you do, using a DI container means they did it right. False! The DI container only guarantees everything is hoisted up to the object constructor. This is NOT DEPENDENCY INVERSION. The objects can be in the constructor, but the dependency arrow can still point the wrong way. An obvious example of this I've seen a million times is an interface sitting right next to its only implementation. That's NOT Dependency Inversion.
If you don't use a DI container, the problem also may or may not be solved, but it is never obscured, it is completely visible.
It's not a typo - without DI (i.e. by using new operator freely whenever dev feels like) code still has dependencies, just this time hard-coded and invisible until you read the source.
Also, one can use DI without DI containers. Actually I prefer not using DI containers when possible.
That multiple usages of the same concrete types requires changes on multiple places. For small programs, the di container itself can probably be skipped (i.e. instantiate an object graph by newing up). For larger programs, this is too cumbersome and therefore error-inducing.
Yup, unit tests, and in my mind, that's bad. There's two reasons:
#1 Because the language provides no means to arbitrarily mock fields of a class, an entire design pattern and framework needs to be created just to do so. Why can't the language just have such a feature?
#2 This actually temps people in building worse design, because instead of creating pure units that don't have dependencies to begin with, it facilitates the opposite of creating deeply nested chains of dependencies, because "DI framework wires it up for me". Where if you didn't have that luxury, you'd be pushed towards pure units that don't have state dependencies, and integ tests instead, which in my mind is much better overall.
#1 is just moving the goalpost. You jumped from "why new is bad" to "why can't language mock fields".
#2 What stops you from creating a complex system without having dependencies, thus avoiding DI? Also, I think it would make an interesting case study if you're willing to write it.
> #1 is just moving the goalpost. You jumped from "why new is bad" to "why can't language mock fields".
I don't think I did. Someone says, well if you use new, how are you going to unit test your class?
And one answer from a user of the language would be: Ya, I guess you'd need to change the way you're creating dependencies so they happen in the caller, and then you'd need to change your class so it takes the instance on its constructor, etc.
And this is the original OPs issue with C#, all the ceremony involved.
Another answer would be that the C# language designer could build a language level feature that avoids having to do that and allows mocking new inside a unit so it can be easily tested without shenanigans.
In fact, in Java-land, there is a library that lets you unit tests classes that use new, it's called Powermock. So it is feasible.
> #2 What stops you from creating a complex system without having dependencies, thus avoiding DI? Also, I think it would make an interesting case study if you're willing to write it.
I've moved to a functional language instead personally. That said, I do still like C# and Java, and think they are great languages. I'm also not against DI, or other enterprise patterns, some have legitimate uses. But I have definitely seen what the OP is complaining about, those languages are too deep in their own rabbit hole sometimes. To the point that half the developers don't know why they use DI, that's just part of the template they used to bootstrap their app. There's no thought process about, wait, what is the issue in my code structure and design that I'm facing, and what could I do to solve it. Instead it's just, I'm using all the "best practices", they are the best, and I'm using them all, all the time, so my code is the best it can be. Without any thought about what advantages they're even getting out of it.
That's why I asked about the "new". Most people don't really know the pros/cons. They only repeat what they read blindly: "New" bad bad, no use "new", never, very bad, antipattern, here link proof, must use DI instead.
And sometimes when they know of a cons, they don't think about tradeoffs against alternatives. They just say, well it has this one con, so it's very bad! An antipattern!
I think that creates a setting where you get monstrous framework and code bases, where people just throw every known pattern at it, no matter if it was called for or not.
I spent maybe 15 years using C# and DI (for most of that time), and understand it pretty well. I still dislike it.
> just they'll be hidden and tightly coupled
They're more hidden with DI than without, in my experience.
At any rate, having left the .NET stack around 5 years ago, I certainly don't miss DI. My current code has more and better tests than my C# code ever did, so DI didn't really help me there (nor did it hinder me-- it was neutral). But my current code is much more explicit, direct, and a fair bit more compact. I'm definitely happier with it.
Compare 2 classes. Class A expects all its dependencies to be passed as constructor parameters. Class B has a default no-arg constructor, but buried inside 5 of its methods calls Dep1 dep1 = new Dep1(); or Dep2 dep2 = new Dep2(); Now, which class has obvious and loosely coupled dependencies? Which class has hidden and tightly coupled dependencies?
To me, answer is clear - class A has obvious and loosely coupled ones. Lo and behold, class A comes from program which uses DI. Class B doesn't.
This is the common misconception/defense of DI that drives me so cRaZy I've jumped to different languages. DI containers do NOTHING, NOTHING to make Dependency Inversion easier, nor does use of a DI container guarantee (as dozens of projects I've worked on prove) that DIP is correctly adhered to.
DIP is achieved precisely when the components (be they packages, projects, classes, types) are organized with the low-level modules depending on the high-level modules rather than vice versa. In English, this usually means the I/O and any heavy framework code is kept out of the program logic.
What DI does do is confuse this matter. Class relationships which are mere IMPLEMENTATION DETAILS of a module are elevated alongside the actual component architecture of the program. The object graph becomes obscured. Program execution order becomes nondeterministic. The callstack is completely ruined, undoing decades of enlightenment since Dijkstra's "GOTO statement considered harmful." And all my experience with the pattern proves, the DI container causes programmers to not even evaluate or understand whether their dependencies are even inverted, because all classes and their relationships just become one amorphous blob floating aimlessly inside the container.
Some fair points but I would disagree on the fundamental definition of the DIP. In my book it absolutely does not mean that low-level modules depend on high-level modules. That's just high coupling turned upside down. You have to have shared abstractions as the "loose coupling" between modules and nothing more, to say you are adhering to the DIP.
Simply, this means that no implementation module, high or low, depends on any other implementation module, ever. It only ever depends on abstractions which are shared between modules.
I don't see how using a DI container can change the program execution order, unless one is misusing it terribly - after all, its sole purpose is to provide the correct concrete implementation of a dependency to an object at the time of its construction, the execution order from the perspective of the program is 100% preserved. Sure, maybe the container itself creates my graph in a non-deterministic way, but why would I care? If my program depends on this that's just bad design imo, no amount of libraries is going to save it :)
And the object graph is not obscured, in fact it is clarified, because you look at a class constructor and immediately can see what it's invariants are! And I have yet to come across a DI library that wouldn't immediately halt and catch fire if you introduced a circular dependency chain, so it's literally not possible to have these amorphous blobs (great expression though!) in any proper DI container.
> DIP is achieved precisely when the components (be they packages, projects, classes, types) are organized with the low-level modules depending on the high-level modules rather than vice versa.
+1. This simple idea is the basis of Clean Code, Hexagonal Architecture, Onion Architecture, Haskell, Functional-Core-Imperative-Shell, among other good architecture ideas.
Page 150 of "Clean Code" says this about the DIP: "In essence, the DIP says that our classes should depend upon abstractions, not concrete details". This is very similar to pretty much any canonical definition you can find anywhere else. I'm quoting it here because the person who coined the principle is the same person whose name is on the book, so I am guessing his definition is correct.
If you're not using abstractions, you are not using the DIP. What is being described here with low-level modules depending on high-level modules is categorically not the DIP, and I am starting to wonder if this is perhaps part of the frustration that the poster we're replying to has experienced with DI, since turning your coupling upside down will have the same problems as just high coupling in general, except everything is upside down now and harder to read :)
Hi chunkyfunky. I recently read Clean Code, great book.
I agree with your take on DIP.
We're using Microsoft Service Collection and with the Scrutor nuget component we were able to easily decorate an implementation from another package to extend the functionality adhering to the open closed principle OCP.
DI also enables your code to be open for extension but closed for modification.
Scrutor looks great, I must take it for a spin! And that is a great point about DI - being able to extend code/behaviour by injecting a different implementation of a dependency
One of the issues with solving a problem via constructor injection is that anytime you need to access a new object in the class is you need to DI it. We'll now you have to go change a 100 unit tests that are failing because of this.
I am not sure what is a better way to solve this problem, but the original OP is right - it's far too much ceremony.
Yes, this can be a problem, that is why I have come to the conclusion that unit tests in larger projects (not libraries) is mostly a waste of time, because as soon you change the dependencies of a class you should rewrite your unit test for that class anyways because the class is not really the same class anymore (it works differently), you are just keeping the class name for convenience. And there is always a danger of changing a unit test, are you sure you are testing the same thing?
I think it is ok to depend on a DI-container in your unit tests for dependencies you are not directly testing, instead of stubs or manually instantiating them, to avoid updating unrelated unit tests when some class changes it's dependency somewhere in your project.
Instead I prefer system/integration tests where you test lets say a complete request from start to finish. Now your code base can change as much as you like, dependencies can be added or removed or classes or packages can be completely deleted, the tests stays the same. And now you are having tests that much closer matches reality instead of tons of mocks and stubs that can easily fool you to believing that they represent a good estimate of reality.
By doing this way a constructor based DI does not become a nuisance instead it helps you to organize your project, as it should be. Using a DI container makes it natural to break down your classes and automatically share the same decencies between them.
Counterargument to my argument is that if you don't use constructor based DI you can rewrite your class in anyway you like and the test stays the same, that is all true, however what DI gives you is the possibility to change the behavior of a class from the outside by injecting different dependencies, this makes your code better structured and easier to reuse.
If you want to accomplish that in the non-DI case you have to go thru trouble of configurable proxy classes which just introduces one more layer of bureaucracy, the thing you wanted to avoid by not doing DI.
My own criticism of DI containers is that they introduce magic to the project, personally I truly dislike code magic, however a DI container is nothing more than on the fly factory creator. In theory you could remove the DI container from a project by manually writing factories for each execution path, for me that becomes an acceptable level of magic as long as the DI container doesn't do lots of other things that factory couldn't solve.
Sometimes I find this useful. This should also help the test errors you mention.
public class MyTest()
{
private readonly IServiceProvider _serviceProvider;
public MyTest(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void MethodA()
{
var serviceA = _serviceProvider.GetService<IServiceA>();
}
public void MethodB()
{
var serviceB = _serviceProvider.GetService<IServiceB>();
}
}
> the reason for DI is to support the Dependency Inversion Principle : "High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
You can still depend on abstractions without DI. Just use an interface or asbtract class in your code.
DI only plays into how you acquire yourself an instance of the concrete class for that abstraction you depend on.
So it can be given to you by your caller (DI). You can go fetch it somewhere (ServiceLocator). You can create it through a utility (Factory). Or you can create it yourself old fashion way with new.
> But you don't have to use a library; many times I'm writing a simple-ish console app and just use good old "Poor Man's DI" where I manually construct my object graphs at startup
I will support that. I'd like people to be precise in their criticism, do you find the DI pattern troublesome, or some particular DI framework?
Neither, I'm a fully paid up and card-carrying DI-club member :)
I was kind of making the same point as you did, that you don't need to use any kind of DI library to achieve the DIP, but that in something like ASP.Net it is there and simple to use.
Not having used .NET recently, is what they've implemented something like Spring? The description of DI everywhere and inscrutable failures at run-time remind me of that.
Not sure how Spring does it, but for example in ASP.Net Core, it's really simple. In the app startup class there is a method that's automatically called by the framework whereby it supplies a services container. In this method you "wire up" your interface->concrete mappings
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
}
Later on when for e.g. an APIController is instantiated, once you've declare a constructor dependency on IMyDependency, it gets resolved for you automatically and whatever concrete class is mapped to gets created and passed to the constructor.
Outside of ASP.Net (console app for example) you can either do this manually, or you can get a ServiceCollection object from the framework, or you ca nuse a 3rd party DI library, etc.
Generally speaking if you don't configure this correctly you get a pretty simple message telling you that the container could not service the request for an IMyDependency or whatever and then it's pretty simple to figure out what you missed in the wire-up.
If you understand Spring then Asp.net Core will feel right at home. I think .Net tends to be more explicit in the configuration, with DI being no exception. For instance, in Spring you can just annotate a service with @Service and the DI framework will pick that up. In Asp.net Core, you actually have to say IThing is backed by Thing as a singleton when running the application and TestThing when running tests.
Edit: Also as others have noted, constructor injection is the way to do DI.
This was going to be my question. For your own classes and services, nothing is stopping you. You can new up classes without DI all day long. ASP.NET does layer on it's things using DI, but it's boilerplate built into templates. For example, OOB you may use the built in UserManager to authenticate users and store their info in a db. If you want to customize that and NOT use the built-in, you may have 10-20 lines to write in Startup.cs to inject a different UserManager or configure the built-in one differently. I've never felt it was massive or wasted my time vs Node/express work I've done.
OMG! I was wondering you might be proposing this. You should not be using "new" keyword anywhere in your code. If you have hard coded implementations like that in your code then how will you mock them in unit tests? I hope you'll not reply-with saying "don't write unit tests".
I am a big fan of DI, but you can often get a lot of useful unit testing done when you try and keep I/O mostly seperate from the rest of the code. If you have ever programmed in Haskell you will know what I mean.
"OMG! I was wondering you might be proposing this. You should not be using "new" keyword anywhere in your code"
That sounds like dogmatism, and I'm going to take a special note of the emotionally charged language you're using here; it's symptom of some of the underlying issues I see in our industry (more to be said on this).
That said, I do agree with the sentiment in general, but you can (and sometimes should) use the `new` keyword in your code, especially when writing tests. In some cases, it makes testing so much easier and helps with the maintainability.
"I hope you'll not reply-with saying "don't write unit tests"
After 2 decades in this profession, the only think I'm sure of is there is no "silver bullet". Of course, as a general rule, it's a good idea to have a test coverage of your codebase but sometimes it's not valuable and not worth adding it. So it's a "it depends" from me!
Unit tests don't do anything to validate the existence of DI. Most people don't even use DI in their tests. DI is just spaghetti code.
The main thing holding C# back is the culture around the DI and weird hostbuilder code associated with ASPNET. If you just skip that part, it's great. Unfortunately a lot of web examples, solutions, and code libraries themselves are ASPNET-focused, and more and more of the code becomes some truly bizarre, unreadable framework injection in an inside-out fluent builder.
Do you mean DI (dependencies being added through the constructor abstracted by an interface) or DI Containers (magic code that magically creates objects for you)?
What exactly is the issue with DI? How else are you going to pass dependencies? How are the errors "inscrutable" when they tell you the exact type that it can't load?
Do you just want to `new SomeClass()` instead? If so, what's stopping you from just doing that?
//MyController.cs (or Page, etc) and others get to use it with no ceremony:
public MyController(MyService foo){ this.foo = foo; }
vs no DI, where the config "goo" has to be repeated in every place I intend to use it (or in this case it could also be buried in MyService.cs):
public MyController(){this.foo = new MyService(new MyDbContext(ConfigurationManager.GetConnectionString("foo")));}
public Dispose(){this.foo.Dispose();}
If I knew MyService would never be swapped out and only ever used in one place, I may opt for the no-DI route. I'll often start there then move it to DI when I want testability, reuse of config, plugability, etc..
The way to manage this without DI would be a static method someplace (Startup.cs or elsewhere) which creates the new MyService, stores it, and returns a reference. Then any controllers or pages that need the reference call the static method.
The method could return a MyService, or an IMyService if you want to be more flexible. It can always create a fresh object, manage and return a singleton object, or manage a pool of objects. For the new and pool cases, you can have another static method for 'returning' the object when the controller/page is done with it, so you can manage disposal or pool availability.
The biggest advantage I see to this approach is in debugging: there is a clear stack trace back to the source of the object, and also to its implementation if you're not using an interface. With DI you often can't tell where an object came from, and sometimes it takes some digging to even find out its exact type (unless you only have one implementation of each interface, which is often the case.)
It seems like GP meant a static method specialized to whatever resource the program needs rather than a pluggable factory. All of the issues in that post appear to arise from the fact that they expect the locator to need configuration before it will work, while my reading of GP is that their configuration is which type the method returns, no registering a class or allocator necessary. Yes, this restricts you to a series of services known at build time, but... it seems to me that you knew what your services were anyway, whether it was directly in the locator, in an xml file, or in a registration call.
ServiceLocator has you create a runtime service registry where you can dynamically register and fetch instances too and from. And often that means it bypasses static type checking, as you might be adding and fetching services by name using a string.
This is different in that it's static. You don't add a service to it, it creates it the first time you ask for it (if singleton), or everytime you ask for it (otherwise), or pools it, etc.
I recon some similarities, but most enterprise "service locator" involve a lot more than this, and often are meant to give you this runtime dynamism where you can load and unload services as the app is running, etc. making them way more complex for apps that don't care about this.
Actually, as written, myService never gets set, but I think I know what you mean. This is not thread-safe and assumes you want singleton. To use it you would in each controller do:
var service = Startup.getMyService();
And potentially know if the controller should dispose service.
This doesn't seem much less "in the way" or "massive" than:
Absolutely nothing in that blog post applied to what I intended, because I did not say that a generic method that looks up implementations in a registry should be used. My static method would be explicitly coded to return a particular implementation, and there would be a separate static method for each kind of service you need. (Eg: one per interface if you're using interfaces for the return types.)
No registration or configuration is needed, because the types are hard coded. If you need mocking support, a single IsTest config setting could be used in a conditional statement to determine which implementation to return.
That's very little additional coding if you use a ternary statement, and it lets you opt-in to the mocking mechanism if/when/where you need it, instead of permiating your entire architecture with it.
> Then it's just a massive waste of time and gets in the way.
I find it very useful for several purposes that have nothing to do with mocking or testing.
> and have become blind to just how much extra code it generates
It doesn’t generate any extra code at all.
For the record, I think that built-in .NET DI is pretty bad. It would be better if they managed to avoid including that abstraction in the framework, even if I personally choose to use DI in most of my projects.
Forget mocking, the major benefit of DI for me has always been proxies.
For our multi-tiered system, this means managing caching and logging of certain calls and events at the level of data access. And at the service level, it means dealing with things like authorisation.
Smart use of DI sets the system up in a way were it's easy to handle these types of cross-cutting concerns where appropriate, and with minimal boilerplate. It also simplifies refactoring.
To be fair there are some very questionable design decisions with ASP.NET Core. The whole way of initialising a Host and configuring things around it is a huge myriad of nested factories and builders which honestly looks like a huge mess. It's hugely overcomplicated for 95% of web applications in my opinion.
I recently ran through the entire ecosystem thinking that there must be something akin to Sinatra (micro web framework) in .Net. Nope. Every single thing is built on top of ASP .net.
I did find a WIP library where they are trying to go for a low ceremony framework https://github.com/featherhttp/framework, but even that is built on top of ASP .net
For me the worst bit about .Net is their treatment of F#. IMO, it is one of the most practical and usable functional languages out there. But none of the official docs have anything on F# at all.
Here is an example doc; see if you can find any code example for F#.
https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor...
Its always C#. And I have 0 interest in that kitchen sink language.
Between ASP.Net and C# being the entire world for .Net, I have really not much interest in using it for anything. There are plentiful of interesting languages and ecosystems that I can use for my small time projects.
API's are the same for F# and C#, so it should be trivial to translate the code examples from C# into F#. Just remove the curly braces and change "var" into "let".
1. For a new comer to the language, this is still too much of a hurdle.
2. If I wanted to use F# only as a proxy for C#, then I would rather use C#. I want to write F# code in a functional first and idiomatic way.
This is the stock answer I have gotten in the .Net Community and as someone who wants to use F# for web apps, its a huge turn off. There are pretty much no tutorials, no recent articles and no performant native F# libraries out there. There is no push from MS to improve the situation. and the community is like - This is fine!
Expecting a new user to write c# code with "let" isn't going to fix that.
> There are pretty much no tutorials, no recent articles and no performant native F# libraries out there.
Giraffe [1] is the de facto standard F# web framework. It's a plugin on top of ASP.NET (for all the reasons described elsewhere - performance, security, ecosystem) that lets you write functional-style 'route >=> function' code without controllers.
If you want a more batteries-included approach, Saturn [2] builds on top of Giraffe to provide, well, more backend batteries (database layer, etc.)
Finally, the SAFE stack [3] are a set of project templates that combine the above with a F# frontend (via the Fable JS compiler) and some Azure deployment helpers.
F# deserves more love in their communication. Definitely. F# appeals to many, however, from a product management perspective (aka MS perspective) there is a ratio of 100 C# dev to 1 F# devs (MS showed statistics once on it).
Also, F# is community owned to my understanding (up to the statement of a MS Research Employee and F# inventor Don Syme to say that the "Microsoft." prefix on ASP.NET Core are not open source community friendly) while C# is 100% design & developed by MS.
Which - IMHO - makes it very delicate for Microsoft to communicate well here.
They also have a few high profile personalities that are pretty much into the "us vs them" towards C# developers and Microsoft, which turns off many enterprise developers to even bother with F#.
This would remove the benefit of functional idioms, and mitigate any advantages the differing approaches have. F# and C# and not just syntactically different.
True, but the code examples in the BCL documentation is to show how to use the API, not to teach the languages. See something like F# for Fun and Profit https://fsharpforfunandprofit.com/ to learn to write idiomatic F#.
More F# documentation and support from MS would be great, but IMHO F# examples in the BCL documentation would not be the best use of resources.
NancyFX was a Sinatra Port for .NET Framework. I belief the current .NET Core variant is called Carter.
And for sure, they are all based on ASP.NET Core. ASP.NET Core is extremely modular which allows, e.g. a middleware stack like Sinatra being built on top of ASP.NET Core's Kestrel webserver (which ... cite once again Techempower benchmarks) beats everything nearby in the current situation. It would be a horrible choice to built a web server framework right now on .NET without using Kestrel. You will never achieve to get close to the performance of that.
It is technically separated out. Just shipped with .NET Core in a big framework bundle called ASP.NET Core (which is in all containers etc anyway).
Look at the techempower benchmarks implementation .. they run as close as possible to Kestrel. They have variants with raw kestrel interface, some with low-level middleware (I guess with express like routing) and then full-blown MVC like comfort.
Low ceremony? Complexity in real world applications requires complexity in the frameworks that address them. Users will be more disappointed by simplicity that leaves them addressing complexity without guardrails, conventions, and tooling support.
If your view of low ceremony is matching HTTP verbs to methods, you may happily code your own against the HTTP primitives; if you want a framework that abstracts the complexity and provides a shiny-happy path, you may have to allow for some ceremony. The tooling is the antidote to this.
I think the place you trip up here is in not wanting to use a batteries included approach, while using a framework that makes the necessary abstraction/complexity tradeoffs for projects of mid to large scale. The appropriate tradeoffs _cannot_ be one size fits all.
F#'s support is tricky, as the number of users here is dwarfed by those in C#; and although .NET supports this language diversity, there are no direct idiomatic 'conversions' possible between C# and F# in many cases. This multiplies the effort of providing documentation and examples. I too would like to see it treated as a first class citizen, and for that - the code is OSS.
> I think the place you trip up here is in not wanting to use a batteries included approach, while using a framework that makes the necessary abstraction/complexity tradeoffs for projects of mid to large scale
You have hit the core of my issue here. I am more used to the node-express or golang's way of working where you start off with the smallest and simplest scaffolding and then add on the bits that you need. Rails and ASP .Net are not something I want to use for my small time projects.
Its probably unreasonable for me to expect .Net to be something completely different from what it really is, but I really hope that .Net grows up to enable other way of using it.
(ASP).NET is also like that. The default templates just include more to start with since the platform is designed to scale to many scenarios and features.
You can trim out a lot. Take a look at the techempower benchmarks to see examples of different usage of the framework.
> Complexity in real world applications requires complexity in the frameworks that address them.
Does it? The frameworks I use today are really, really simple. Polka on Node and Go + a few helper libraries. It's all really simple and clear what's going on, and I can solve problems of just about any complexity I've come across with just those basic tools.
XSRF, rate limiting, permissions/RBAC, feature toggles/TIP, analytics, profiling, silly examples like recent samesite changes - Just a few examples, and that's without touching the developer tooling.
As each of these orthogonal concerns starts drifting in, applications become more and more complex as they 'tack on' support for these in code, rather than having structures in place to allow for extensions and modifications.
Abstractions always have their cost. Complexity exists in the problem domain of the web, and always will. .NET Core is generally well architected in response to a long experience in enterprise and developer concerns. It's not a magical answer to every domain's requirements, but it's generally a damn good compromise ime.
Every one of those things is just composable middleware in the Node/Go stacks, and I think in Phoenix and most FP stacks. Middleware composition seems like the right approach to me.
You can use .NET without ASP.NET. You also don't have to use all of ASP.NET either, it just comes with a full featureset and the default templates are configured for easy productivity. You can bind directly to sockets and write TCP responses using the fantastic memory and networking code, or use the robust Kestrel webserver to write low-level HTTP responses, or go up the stack and let it handle everything while you only deal with objects.
.NET supports as much ceremony as you want to deal with.
> You can use .NET without ASP.NET. You also don't have to use all of ASP.NET either, it just comes with a full featureset and the default templates are configured for easy productivity
This is only true in theory, but couldn't be further away in reality.
Microsoft suffocates everything which is not built in-house 1), so there is absolutely no OSS ecosystem going on in .NET which would allow a choice of different non-ASP.NET web frameworks.
Other languages have a wealth of different web frameworks and some are super basic and lightweight and others are huge with batteries included.
You don't get that in .NET at all. Everything is ASP.NET Core, and ASP.NET Core is a typical Microsoft product - an attempt to build a one size fits all solution, the magic silver bullet to solve everyone's problems at once. Like with all silver bullets, when trying to please everybody you end up pleasing nobody.
1) Except F#. In F# you will find a more vibrant OSS community and non ASP.NET alternatives (most famously Suave), but only because Microsoft doesn't care about F#. Instead of creating an equal level playing field for C# and F# they are morphing C# into an F# hybrid and eventually will stop caring about F# even more than now.
I'd say it's the community that suffocates everything not developed by Microsoft, not Microsoft itself directly.
It's probably a cover your ass situation, especially at the larger, more conservative, enterprise-y, non-tech companies .NET is frequently used at. No middle manager would get fired for suggesting to use a Microsoft developed & endorsed solution instead of using some hipster open source framework.
Well they're basically turning C# into Scala, and Scala is (rightfully) dead/dying, can't die fast enough IMO, so maybe they are just hastening the demise of C# so F# can take the throne.
It's completely true in reality. I've used .NET for the last 4 startups and use various layers as needed for performance and flexibility. I know many other people and companies doing the same, and it's one of the main advantages of how much .NET gives you to pick and choose from.
ASP.NET is not a single monolithic thing, the Core evolution has made everything modular and you can plug and play the parts you want. You can use just routing to translate URLs to endpoints, or just the view engine, or just the serialization bindings, or just razor pages, or just MVC, or just low-level HTTP responses, or just GRPC, or anything else. The entire pipeline is a series of middleware, which itself layered on the Kestrel webserver, which itself is layered on low-level sockets wrapped with Pipelines and Memory APIs.
There is no one-size fits all, and asp.net explicitly recognizes and solves for it. The issue might be your expectation that there is a small/light and large/heavy framework. There isn't, you just make one to fit you.
On the first part - I agree - and it's exactly this (MS's overwhelming influence on forcing direction) that keeps the .NET ecosystem back.
For ex, AFAIK, there was no community participation on DI Design and community feedback on DI design was rejected. Then it was made the default in ASP.NET.
ASP.NET is pretty bare bones, all by itself. It's pretty much just a simple pipeline of request handlers with DI support (which is also optional). MVC, routing, Razor - all unnecessary if you don't want to use them.
I agree that the ASP.NET documentation is lacking in F# code samples (although the example you picked is a bad one as Razor doesn't support F#, and so naturally wouldn't have F# code samples). You might find that Giraffe is a more comfortable framework, as it's essentially an F#-ified shim on top of ASP.NET.
A lot of this thread feels like Microsoft's terrible naming having not surprising negative consequences. "ASP.NET" can mean so many things to so many people.
To me it's usually a synonym to the full-fat MVC with controllers, DI, everything. This is reinforced by most of the dotnet templates and documentation, which make it feel like if you ask for a banana you're given a gorilla and the entire forest whether you want it or not.
In comparison, if you look at Go you'll see the "net/http" package. It's the HTTP server. Extendable. Built in - not something to download from NuGet. Only does HTTP - not reinventing its own DI, configuration management, IIS, etc.
You can gradually apply DI: if you don't want to register all of your components that's OK. Still, I think it's very useful and lightweight on .net core/.net 5. Python's Fast API has some kind of lightweight DI too... and that's great.
The .Net Core documentation is indeed a dumpster fire. As a longtime C# guy I can't even find the up-to-date "blessed" way to do things with ASP.NET anymore. They change it every version it seems. The DI/IoC stuff is definitely too pervasive, particularly in an ASP.NET context.
That said, the new C# language features really do reduce boilerplate when used judiciously - multiple return is now available, "expression-bodied properties", improved template strings etc. Lots of good stuff that is hard to find good documentation for :)
Oh man, flashbacks to when Go style channels were added. I ended up digging through the code in github, and looking at examples from MS devs on twitter to piece together how they worked.
Most people really like the Ionide VS Code plug-in for F#. I still think Visual Studio is better, but VS in its full glory is only available on Windows. Ionide should work for you.
As far as starting up F#, I try some of the resources here https://fsharp.org/
If you want to cut down on the ceremony and use F#, check out Giraffe or Saturn.
If you want to build apps in an entirely different way that looking nothing like the wretched enterprisey nonsense of the C# and Java worlds, check out SAFE: https://safe-stack.github.io/
I think it's a result of MicroSoft shifting to the mindset that performance is a feature.
I have been a .Net developer for about 15 years and remember when it was a violation of terms of service to publish .Net performance measurements. Especially performance comparison against Java was something that would get you on Microsoft's bad side real quick.
So it's really I think the difference between performance being a secret, and it being on the front page and Microsoft throwing significant developer resources into it.
And, not forget, the cycles weren't theirs. Now that everyone and their dog is running webapps on Azure, suddenly cycles/request is a metric worth optimizing for, since they're billing per request but spending per cycle.
Not sure how .NET holds up against Java but for the last decade or so its been common knowledge that .NET on Windows was way way faster than most of the interpreted web frameworks out there (PHP, Ruby, etc etc). Compiled vs interpreted, its pretty obvious. This 2013 post about Stackoverflow being served by just two IIS servers is fairly legendary: https://nickcraver.com/blog/2013/11/22/what-it-takes-to-run-...
My point being, I thought .NETs performance was pretty well regarded already fwiw.
a lot of the standard library has been updated to use simd where possible, there are new features for dealing with memory that let you get c level performance for more things, this has been leveraged to move some things in the standard lib from c to pure c# which plays nicer with the GC for big wins. the jit has also been improving what things can be devirtualized, and linq has had some performance tweaks
A lot of people’s opinions about dotnet are not informed by anything, they see Microsoft in the name and decide to drop snide remarks about how bad it must be, as you can see already from this thread.
I think that claims about a thing should be based on observations concerning that thing, and not based on a folk memory acquired through other people’s attitudes and biases.
techempower is the go to web framework benchmark suite. it is of course gamed heavily. c# does as well as any garbage collected framework, half as fast as the best rust/c++ ones
The benchmark has to be read with some knowledge. For example Plaintext is given you an indicator how good the runtime and the HTTP stack is. JSON goes one step further and gives you the comparison with common REST APIs including JSON serialization. One step further is then there is Fortune which also includes some DB layer and should representative for applications.
However, also check the definition of the rows. With Plaintext there are 3-10 entries for ASP.NET Core with different level of comfort (from "do not do that at home", to express like middlewares to "rails like MVC")
I'm not sure where on earth you have got this notion from; the .NET Core team have been performance focused for a long time now, and blog posts from the .NET team detailing perf improvements have been posted on HN multiple times.
You even have hardware intrinsics available to play with, and for performance-sensitive libraries such as hashing algorithms, it's typical for performance to come within spitting difference of RAM speed.
"I'm not sure where on earth you have got this notion from"
Where on Earth? I could tell you precisely where if it mattered. I'm certainly glad the team at MS has been focusing on performance now because they certainly didn't when the team I was on decided to try it. (Hey, maybe they could help out the Visual Studio team as it takes like 10-15 seconds to open the app to an empty screen).
I haven't run any tests lately (and probably never will as I'm not interested in a language that emits IL rather than machine code; or a language that uses a GC) so I apologize for throwing out the 15x number when it is probably not correct. It won't happen again.
I would wonder why some game studios (like Supergiant Games / Hades) ended up porting their C# game engine to C this year? Was it because C# was fast enough? Or maybe it was to make the game more portable to other platforms? Not sure.
Great, but I would rather see a perf comparison of .NET 5/Core vs .NET 4.7 because .NET Core was a new technology that may have traded performance for features in its first versions. Similar to how JDK 14 beats JDK9..13 but still in many cases is behind JDK8.
I think that on the contrary, .net core already had much better performance but less support for some desktop windows stuff, and .net 5 actually added most of the remaining features back.
.NET Framework had a outdated Web stack (System.Web over IIS). In 2014 it was 13 years old with little chance to improve anything without breaking someone. That was the whole reason why .NET Core became a thing.
.NET Core in Version 1.0 cheated then by just taking the network layer of node.js called libuv and linked it up (C -> .NET) . In Version 2.x they wrote and independent socket based networking layer straight in C# which outperformed libuv significantly. In Version 3.x they switched over to use new low level runtime primitives (e.g. Span<T>) which could never have been delivered in the .NET Framework (no breaking changes!). Now in .NET 5 more runtime improvements were added.
Span can be used via nuget also in .NET framework. It’s slower than in Core because of lacking runtime support, but not by that much. SIMD also was already available since 4.6 and unsafe was always there.
I think what really changed was the mentality. Before low level stuff was tried to be contained and used mostly for interop. Now the value of the performance of safe low level primitives is seen.
I don't know any definite benchmarks, but anecdote, the unit tests of a bigger library[1] I am writing run in 10s on net5 vs 12s on dotnet 3.1 (core) vs 17s on .net48 (framework), on the same machine with warm caches, of course. That's a sizable difference. YMMV
[1] not web stuff, "plain" C# essentially. It has some compute-bound things like encryption and compression and some fancy tree structures, uses a lot of linq in certain parts.
I remember trying it out (on Linux, that is), and really liked the concept, but it was painfully slow, so it was no fun for me. Like, waiting 3-5 seconds in the REPL for a single line to compile and execute.
PowerShell has never been that slow for me, if you wouldn’t mind you should probably file a bug about that if you’re still experiencing it. I have found it to start slowly sometimes but once it’s hot I have no performance issues.
It will become somewhat faster as soon as it upgrades due to the improvements in the class library. However, I doubt that PowerShell performance relies significantly on .NET itself. It is PowerShell who needs to be implemented efficiently.
There has long been this perception that Go is a low level language. In the sense that it isn't rich in complicated features, this is true. But it is a garbage collected language, it manages it's own stack which is less low level than C#, and it doesn't provide any low level control over memory/instructions that C# doesn't, less in fact. And the cost to interop with C is higher with Go than with C#.
I think the fact that Go compiles to native executables as the default, just gives the impression that it is "low level". That and the lack of features I suppose.
I say all of this as someone who loves Go as a language by the way. C# could use some simplification!
Go is faster than .NET and the code is shorter. On average, results may vary.
I looked at the benchmarks.
In cases where .NET is faster it's because the C# version is optimized using unsafe code and x86-specific (i.e. non-portable) AVX2 intrinsics to do the math.
Go version is written in straight-forward way. I think all Go programs are shorter than their C# version.
Alternatively, we could do apples-to-apples comparison of the benchmarks.
Go and C# are so similar that for benchmarks you can transliterate one into the other.
We should be comparing programs that are comparable.
My point is that BenchmarksGame is not comparing comparable programs.
Code for some languages has extreme optimization, including doing things that most people don't do in day-to-day programming.
Go supports assembly. Given enough time I could probably implement a given benchmark in assembly, beating C# (and pretty much anything).
This is used to good effect in Go runtime and some really niche applications but in real life I don't have infinite amount of time to micro-optimize my code and write parts of it in assembly.
What I want to know is the performance of competently written code.
Ok so we have some benchmarks that you say are not fair, indicating that C# allows you to write faster programs if you are willing to use unsafe.
And we have an unsubstantiated claim from you, the Go is faster, and code is shorter, on average. What is this based on?
What to you is a fair comparison?
Presumably unsafe code is not allowed for C# and Go assembly not allowed for Go. Are there any other restrictions? We could do a naive port of one of the Go benchmarks to C# and see how it goes, which one do you think is a good candidate?
I wrote a lot of C# and a lot of Go. C# has so much more ceremony that it is more verbose and it shows even in the code we're discussing. You just have to be willing to look.
Fair comparison between C# and Go is actually very easy. Those languages are so similar that you can transliterate a given benchmark from one language into another. Then benchmark those versions.
There's just no way that on an average program Go (language statically compiled to assembly with a very competent code generator) will loose in performance to C# (which compiles to bytecode and then JITs that code at runtime using a much weaker code generator).
> I wrote a lot of C# and a lot of Go. C# has so much more ceremony that it is more verbose and it shows even in the code we're discussing. You just have to be willing to look.
The Go mandelbrot program you looked at is a little larger than one of the C# mandlebrot programs.
I also write both Go code and C#, amongst other things. I find your assertion that C# has lots of ceremony and ends up with more code than Go frankly pretty strange.
Go is well known for having laborious error handling (prompted by a lack of exceptions) and masses of boilerplate and verbosity precipitated by the lack of various other features, the most glaring of which is an absence of generics. This means implementing custom data structures is generally an exercise in copy paste, or ignoring type safety.
Things like the lack of extension methods, JSON and YAML serialisers that require explicit annotations everywhere instead of conventions, the lack of OO/polymorphism, no ternary operators, null chaining operators, etc. etc. also contribute to the verbosity.
Simplicity is good, but the lack of expressivity and the need for such boilerplate is bad. A pretty poor type system makes it worse. Go has many strengths, but I massively prefer C# for the majority of non-trivial situations.
Regarding performance, it depends heavily on what you're doing. For example, Go's allocator and GC is designed to optimise for low pause latency, whereas dotnet is optimised more for high throughput and good cache coherency. Given the extent to which most apps are stalled waiting for memory, the lack of good cache coherence in Go allocations, and the lack of a generational GC can absolutely decimate performance. DotNet is capable of allocating at 25x the speed of Go, for example.
We went form "Go is 50x more lines of code than C#" to "C# code is not that bigger than Go". The OP was dramatically wrong about Go being more verbose than C# when the exact opposite is true.
My point is that if you want to KNOW which language is faster (as opposed to trying to PROVE that YOUR language is faster) you wouldn't compare a C# code optimized with SIMD intrinsics with Go code that doesn't use SIMD intrinsics.
The problem with BenchmarksGame is that it doesn't try to enforce apples-to-apples benchmarks.
It's fun thing to see how far you can push a given implementation if you're willing to spend a lot of time on it.
It compares implementation of the benchmark code, not the quality of the compilers on the code that you'll actually write in real life.
> The OP was dramatically wrong about Go being more verbose than C# when the exact opposite is true.
Sorry, I missed the proof - can you point me to it?
What you presented as "more verbose code" isn't actually a proof - i.e. yes, SIMD code is obviously more verbose than a normal one. And faster.
> Go has support for assembly and supports SIMD intrinsics just fine.
But wait, in this sense any language has support for assembly and SIMD. Bundled assembler is not the same as language-level support for SIMD - and even in https://benchmarksgame-team.pages.debian.net/benchmarksgame/... a large portion of SIMD code is actually cross-platform (what uses Vector<double>), and I am pretty sure sticking to just cross-platform SIMD APIs would be enough to beat Go.
> You wouldn't compare a C# code optimized with SIMD intrinsics with Go code that doesn't use SIMD intrinsics.
You use what's not against the rules, and it's not against the rules on CLBG. You're free to submit your own version of the same benchmark on Go relying on SIMD or whatever you prefer.
> It compares implementation of the benchmark code, not the quality of the compilers on the code that you'll actually write in real life.
Yes, any benchmark is somewhat biased. But honestly, comparing real-life benchmarks is even harder - they involve much more components, so whoever isn't happy with the results can always claim it's a comparison of frameworks, not the actual programs, etc., etc.
Still slower than Dapper though? I like Dapper and we use it where I currently work, but it enables people to maintain their bad practices brought over from raw SQL. I would like to see EF Core narrow that gap in performance a bit
We exclusively utilize Dapper to talk to SQL. We came from EF6 and decided that the training wheels were getting in the way more often than it was worth. Performance was also abysmal but I am confident that has been improved dramatically for EF Core.
In cases where we want to store really complex object graphs in SQL, we typically resort to JSON serialization of those objects. This allows for us to keep the important SQL facts in dedicated columns (i.e. for PK/constraints/indexing) and also have a copy of the full serialized POCO sitting in the last column for convenience.
For reference, these databases (SQLite) are not shared with external parties/systems so we can get away with this sort of heavy-handed denormalization approach. Our use cases of the databases are very well bounded.
In terms of migrations, we just write a simple for loop and use SQLite's user_version pragma to keep track of an incrementing integer version. This means that we actually have a superior solution to EF in that we don't need additional special metadata tables to keep track of this information.
We started a new project at work, and one of the decisions was "No EF", altho we've pulled in some not so nice looking queries (mixed casing / indentation), the performance has been much better, and we can copy the query into management studio and check / optimize it. It's working out well. Part of the code reviews is asking how queries perform.
Being able to validate SQL at code review time is really nice. Seeing the diff in GitHub between old vs new SQL can very quickly tip you off to troubles. Having your DB logic locked up in EF makes this kind of thing a lot more opaque. You could hypothetically develop a code review policy where developers are required to attach a query plan for each modified SQL statement to the PR.
If the biggest problem is whitespace/casing in your SQL, you are probably sitting in a really good position. This is something we do try to be strict about. All of our SQL lives as string constants in 1 file per database so it is really easy to keep track of what is being used throughout in a consistent way. We do not permit developers to write SQL strings outside of these files. It all has to be in 1 place. We have 1 file for all SQL commands that will ever be used against that database, and a 2nd file for its schema migration scripts.
from the perspective of a rust, c, c++ developer it was a bit slow as all GC based languages are. Before this .net core was already arguably the gc language with the highest performance ceiling.
this can of course depend on workload and whether you care about startup time or throughput vs tail latency etc, but on the whole it tended to allow you beat out java, go and other competitors most of the time.
with AOT being a standard option now, getting quick startup time is easier too, along with the other improvements
This is a minor corner of .NET 5 but I'm bummed that .NET's Blazor (WebAssembly) has no way to interact with the HTML Canvas from C#, so you have to write JS interop code, so its quite slow (that is, C# WebAssembly with Canvas is slower than just writing JS).
My hope was that by the time .NET 5 was out of preview, we would have seen something for this so people could write C# for Canvas in WebAssembly. Alas, not yet!
It supports both Canvas2D and WebGL. The API supports batching calls across the interop barrier, so that the interop performance costs are minimized.
As for Blazor being slow: please correct me if I'm wrong, but my understanding, that Blazor code is interpreted, because JIT compilation in wasm is not really possible. The implementation of AoT compilation, which will fix the perf issues is coming along, but it's not quite there yet.
I find it amazing that there are so many incorrect criticisms of dotnet being made in this thread. It’s honestly as though a number of people with barely any experience with the platform have decided they’re experts and come in to tell everyone else how terrible it is. I shudder to think how many other threads about technologies I know less about also suffer from this affliction.
It’s a shame indeed and I hope this site will improve its ability to boost comments with value to add.
There are some interesting and insightful comments even in those under this article though. I find hiding the entire tree once I see a comment that’s just there for the sake of being negative makes it easier to find the valuable ones.
Haven't noticed any improvement in my project, but there is just arrays and 4 arithmetic operations. I guess the improvements are in more complex library functions like regex.
In general you should always be able to get the same performance, but you will have to use less idiomatic F#. Then again, the highest performing C# isn't idiomatic C# either.
I sometimes suspect the whole raison d'être of dot.NET, docker, kubernetes etc, is to render programming into such an esoteric art, that the hoi polloi can be excluded.
--
295 comments
[ 1.9 ms ] story [ 405 ms ] threadThat is, any sort of astonishing improvements is often a sign of the original version being crappy, so it's also a red flag even if it's ultimately the good news.
How is it a red flag? I don't quite understand your comment.
Something like: a Microsoft programmer uncommented
// PERFORMANCE_MODE = true
to make .NET fast.
His comment is just silly :-)
Perhaps not trivial, but it did have easy optimizations - within hours of it being open sourced, external people who had not worked on the codebase were making pull requests with straightforward (and in some cases substantial) optimizations. Matt Warren (author of Benchmark.NET) has tracked many of these.
Since open sourcing performance is one of the goals in mind. And it shows.
I don't think I agree with this, but hopefully that makes it more clear what was meant.
It's more like there's been a few hundred small individual improvements and those provide a lot of improvement in total.
So of course a pointy-haired boss found out about it and you end up with stupid stuff like this: https://en.m.wikipedia.org/wiki/Intel_Upgrade_Service
https://forum.overclock3d.net/showthread.php?t=3671
But also the user you are quoting, the "gamedev trick" is actually somewhat of a true story as well. But it wasn't an optimization trick. It was just different teams that each acted as if they are working on the most important thing, competing for limited old-hardware resources! Some coders, pre-version control, would just pre-allocate ram even if they didn't really need it, just so they don't have to go through the dance of asking other teams to release some. As you can imagine, when the project ended up going beyond the hardware limits causing chaos and stress to the devs, those people would descend as heroes and say "Here I was able to free 512kb!" (from their unnecessary preallocation calls)
Ah, to be old on the internet... :)
Joke's on them, software gets slower faster than hardware gets faster.
That's a question that can't be answered by comparing one version of .Net to another version. Instead we'd need to compare the performance of .Net to a rival system like the JVM. edit Or rather, a JVM.
[1] https://thedailywtf.com/articles/The-Speedup-Loop
Actually, I'd argue they are fixing other people's mistakes with some of this.
A great example are the changes around devirtualization and/or removing certain branches, be it in the code level or at the CLR (.NET VM) level. 5-10 years ago, these changes would likely provide far less benefit, possibly not even worth the effort.
But thanks to Spectre/Meltdown/etc, getting rid of such things (especially near the IO layer) has become more important.
So, what’s the best-organized resource for getting up to speed on .NET these days?
https://github.com/davidfowl/Todos/blob/master/TodoBasic/Pro...
However, especially in the HTTP land, the namespace give away instantly on any sample (System.Web => .NET Framework vs. Microsoft.AspNetCore => .NET Core/.NET 5+).
To speed yourself up when you have previous experience: I think the docs.microsoft.com page is not too bad on that.
First of .NET has excellent documentation, right up there with the PHP. Still my favorite documentation site to snowball through sometimes.
Second, "old DI/enterprisey boilerplate" is what saves you when you get handed the project 5 years down the road, and have to be productive as fast as possible.
Edit:
> So, what’s the best-organized resource for getting up to speed on .NET these days?
Probably just the official documentation: https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcor...
Overall though, I have to agree that the .NET docs are very good.
.NET had excellent documentation. I still pine for the truly-comprehensive MSDN of yore (where "yore" == 2007 or so). Back then, they had less ground to cover (only one .NET Framework and it ran on Windows only), and a bigger budget to do it.
These days, and especially after the big migration to a new CMS a few years back, it's really hit-or-miss for me, especially when it comes to UI framework docs.
They're noticeably working on making it less terribe lately, but at their current speed and considering the work ahead it's going to take multiple years until they have anything anywhere close to Rider today while JetBrains are developing it at multiple times the speed.
Drawbacks: No XAML hot reloading in WPF, lacks GUIs for Microsoft's questionable file formats (resx). That's it.
I should say, as someone who wrote Java/Kotlin for years on IntelliJ, Rider is a welcome addition to the C# world for tooling. JetBrains really does try to produce the best editors they can.
I should mention I use Dart / Flutter, this might not be a problem on Java or Kotlin.
If I don't use the right case it's unable to find the right keyword and most of the time doesn't offer the most relevant option after 'myObject.'
The other day I was joking that my main mode of programming on Visual Studio is pressing CTRL+Space and let the IDE code for me. I can't say the same thing for Android Studio.
I don't know what Android Studio uses for Dart, but it's definitely not that! It's definitely not an issue for Java, Kotlin, Go, or even languages with more complex semantics like Rust.
> Match case - Select if you want letter case to be taken into account for completion suggestions. Choose whether you want to match case for the first letter or for all letters.
Also, there are a few interesting plugins that improve the autocomplete in IntelliJ further, such as Codota: https://www.codota.com (language support varies)
Ruby's site even does that visual thing where you can tell it was made only to be seen on Macs.
https://github.com/fsharp/FsAutoComplete/pull/656
C# 9 also brings top-level programs, which essentially eliminate all ceremony for the entrypoint[0] - an app can now be as simple as:
[0] https://dotnetcoretutorials.com/2020/08/30/top-level-program...As far as I've heard it does not exist e.g when it comes for ASP and I believe that what "OP" meant.
I may be very wrong here
You would of course still have all the usual ASP.NET configuration and DI setup to do though, so the impact would be rather limited.
I meant that (as I've heard) you cannot write big app using "top level style" only/mostly
It probably isnt going to reduce "the boiler plate" that people complain about
For hello world with 29 tokens if i count correctly for C# vs 4 tokens for a Python program, guess where beginners have an easier time to fail?
I used to use LINQPad for that, now I don’t need to, VS Code and this new feature is enough!
Its nice for beginners, not having to explain every weird word they see, but not much for professional development.
However managing complexity with various language features all over the project makes impact.
If you write a few programs per day for throw-away data manipulation or system maintenance or something, then no.
Having this feature aligns nicely with those use cases.
>he very sight of dependency injection configuration is cringe.
I'm curious - why?
Regarding Dependency Injection: It's very much suited towards enterprise web applications; and its failure mode is trying to get DI to work seamlessly in a non-web application sense (C# is a general language; and .NET supports console, GUI, and web applications) and once you get out of the we application thread-per-request pipeline, you find yourself hitting rough patches.
As for DI it can definitely be used fruitfully outside of a web application, I suggest the book Dependency Injection: Principles, Practices, and Patterns if you’re curious. It covers examples from ASP.NET Core MVC to console apps to GUI.
I use EF, but generally only for migrations, and use Dapper for all the actual access. This avoids a lot of the headache I've had in the past with EF tracking, lazy loading, and foreign keys. I also avoid using views for anything, even at work where I can guarantee with many 9's worth of certainty that I will always be using MSSQL. Part of this is because EF doesn't support it for Code-First, but part of it is also generally trying to be proactive rather than reactive and pre-generate anything I'd be using a view for. Being able to more easily switch providers and move to a different RDBMS is just a "perk" that I'll likely never take advantage of.
I despise the Microsoft DI system. I won't bother going into details, it just doesn't seem to work "right" (that is, the way I want or expect it to). On the other hand, I've used Autofac for years and just seem to "get" it, so I sometimes go to relatively great lengths to use it, even when the Microsoft version would have been a lot easier to get up and running.
Personally, I quite like that quite a bit compared to platforms and frameworks where the convention seems instead to be, "RTFM or die (at run time)."
Except in Windows Forms, where the norm is a zero-argument constructor and setting everything via properties, because that approach works with component-based design-time tools. Come to think of it, I think WPF and its descendants do the same.
Passing required dependencies by constructors makes this extremely apparent, not just to the DI framework but to the developers themselves. With constructor injection I can immediately grasp exactly what dependencies an object depends on without even going into the full code flow to look at all the code to find all the IOC container calls.
Edit: Also constructor injection is critical for testing dependency injection. IT allows you to have automated tests that construct every relevant object using the same DI setup as your live application and you can verify that the DI framework can resolve dependencies for everything.
If your DI is lazily done this cannot happen unless you explicitly call all code flows, opening yourself up to runtime errors that may be missed. Unit tests won't catch this because the dependencies of your production site will be different than the dependencies of your test scenarios.
(https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Patt...)
We are currently on a long term process of migrating a large C++ codebase from a service locator model to just passing dependencies through constructors. New code is significantly simpler to both use, understand and test.
Dependency Injection Frameworks have the same issues: - understanding what is going on is harder than injecting objects by hand. - you have limited control over object lifetimes (when objects are created and disposed). Permanent, scoped and transient might be enough most of the time for a web service, but as soon as you fire off tasks that can outlive the requests or when you use it in different circumstances like UI it’s getting a problem.
I've actually worked with C# for over 15 years now and written apps with (in order of age) VBScript, webforms, web services (the MS SOAP stuff), MVC 1, MVC 2, MVC 3, MVC 4, Web API, Web API 2, the OData one I forget the name of and asp.net core. Oh and Silverlight. I've probably missed something. On yeah, WCF. And even WWF! Basically EVERYTHING web-related to C#. Plus enough work in PHP, Python, Ruby, Express, etc. to be able to compare different approaches. And even sometimes VB.net, I've maintained and then migrated entire code-bases from that to C#.
I'm not entirely sure what more familiarity I need?
I'd also note that there are a lot of other people agreeing with my in that thread, almost all of whom show a decent technical understanding. And it's got a lot of upvotes.
But, even if I "misunderstood" or have a "lack of familiarity", that speaks of how poor the design/documentation actually is. If you can so easily shoot yourself in the foot with it, it's not fit for purpose.
To me, it's clear that some people love DI. SOME. Use it if you want. I don't want to. Stop dictating what's "good" code to experienced developers.
If it helps you, fine, but it hinders me.
And I’m still not seeing the benefit of all that experience by the way.
There was no built-in DI container before, you had to use your own like Windsor/Autofac/Ninject/etc, and it wasn't as popular because of it as many people skipped it for smaller/less enterprise projects. I'd say exposing more people do it, even if the built-in DI is rather basic, helped improve many new projects that otherwise might not have chosen to do so.
Any other reasons you think I might be unqualified to talk about the language I've been using for 15 years?
Just to cover some more bases:
1. I've worked in teams
2. I've worked alone
3. I've made apps from scratch
4. I've maintained large existing code bases
5. I've made enterprise apps, e-commerce apps, BI apps, and even just simple websites
6. Millions of people have used my code
7. Apps I made over a decade ago are still being used today
8. I admit I often forget to floss
You never answered what your actual issue with DI is (the concept, implementation, containers, etc), or what your alternative would be, even though numerous people have asked. That makes your complaint seem largely unfounded or a combination of confusion and lack of experience with DI.
Then it's just a massive waste of time and gets in the way.
I really feel that you (and Microsoft) have completely lost sight of the reason why DI was ever even necessary and have become blind to just how much extra code it generates. The only reason was to inject mocks, and people hand-wrung for ages over it because the code to do so infects all the other code. Then people started to try and back justify it with IoC, but it's never really been a convincing argument.
It's also something that if you're not working regularly with it can be very mysterious and when it fails, fails at run-time, quite inscrutably sometimes.
From my perspective it's just boilerplate magic that if you actually need it, fair enough, but if you don't is a persistent, nagging, pain in the ass.
A massive waste of time? Adding a constructor and one line to startup.cs is a massive waste of time? Just how long does it take you?
> It's also something that if you're not working regularly with it can be very mysterious and when it fails, fails at run-time, quite inscrutably sometimes.
If DI fails it’s usually just because you’ve forgotten to add that one line to the startup.cs, and the error you get will tell you that. Can you give me a concrete example of how DI is inscrutable?
In C# the idiomatic way to achieve this is via constructor injection, which is made much easier to manage in ASP.Net with a DI library that works with the framework for you (either the built in one or AutoFac / SimpleInjector, etc.). But you don't have to use a library; many times I'm writing a simple-ish console app and just use good old "Poor Man's DI" where I manually construct my object graphs at startup.
I like C#, no, I adore C#. I have a bad memory for even the simplest method calls and the sheer power the intellisense in a statically typed language gives me to just not care means I can just code with joy.
My personal view on it has always been that someone in MS wrote MVC 1 in response to Rails on the sly. It was fantastic, a breath of fresh air into the MS constant misunderstanding of the web. Especially compared to webforms. For a while, everything was good. Then somehow 7 or 8 years ago the ASP.Net team got obsessed with ramming "best practices" down our throat and everything's gone a bit downhill from there.
And they don't even have a point in this regard, because nothing is forcing you to use the DI. You wanna make a database connection with ADO.NET and a static connection string in your Controller? Go ahead. You can do that. It's no more effort than it ever was.
You want to grow your app to run it in multiple environments and be confident you don't clobber your configuration during migrations? Use ASP.NET Core's DI.
Every app framework has some "magic convention over configuration". I personally think ASP.NET Core's "magic" is a lot less pervasive than in, say Django, or RoR. When I was first learning ASP.NET Core--coming from WebForms--there was a learning curve. You don't just throw everything into a Web.config XML bag of doom anymore, with a single, static configuration reading tool that reads just that one file. That particular magic has changed. I mean, if you want to do that, you can, just read the file yourself. But it's not wired up by default anymore. And the new way of doing things was easily learned with a couple of afternoons of reading the documentation.
Which you can do, because there is actually documentation, and a lot of it. I think part of the problem might be that people are used to other web app frameworks where the documentation is pretty lacking, so they don't even think to go looking for documentation on ASP.NET Core and think they can just jump in and figure it out. I don't think you could do that going from Django to RoR or vice versa, but people complain when they can't go from ASP.NET WebForms to ASP.NET Core without learning new ways of doing things.
Don't like ASP.NET Core? You don't even have to use it. You can (rather easily) write your own web app framework. I've done it in the past, and there are FOSS projects for doing it (https://suave.io/, https://www.abp.io/, https://giraffe.wiki/, https://dotnetify.net/, https://www.dotvvm.com/, https://coalesce.intellitect.com/).
1) that graph of object creation is NOT equal to graph of object usage
2) if you don't use DI, problems won't automagically go away - code will _still_ have dependencies, just they'll be hidden and tightly coupled
It is precisely when you use a DI container that the problem is hidden, whether or not it's solved.
In most projects that use a DI container, the problem is usually not solved correctly, but the developers are oblivious and overconfident. They think, as you do, using a DI container means they did it right. False! The DI container only guarantees everything is hoisted up to the object constructor. This is NOT DEPENDENCY INVERSION. The objects can be in the constructor, but the dependency arrow can still point the wrong way. An obvious example of this I've seen a million times is an interface sitting right next to its only implementation. That's NOT Dependency Inversion.
If you don't use a DI container, the problem also may or may not be solved, but it is never obscured, it is completely visible.
Also, one can use DI without DI containers. Actually I prefer not using DI containers when possible.
Write a util function then, or like some like to call it, one of the many Factory pattern.
https://web.archive.org/web/20200506032508/http://misko.heve...
https://web.archive.org/web/20200502175326/http://misko.heve...
#1 Because the language provides no means to arbitrarily mock fields of a class, an entire design pattern and framework needs to be created just to do so. Why can't the language just have such a feature?
#2 This actually temps people in building worse design, because instead of creating pure units that don't have dependencies to begin with, it facilitates the opposite of creating deeply nested chains of dependencies, because "DI framework wires it up for me". Where if you didn't have that luxury, you'd be pushed towards pure units that don't have state dependencies, and integ tests instead, which in my mind is much better overall.
#2 What stops you from creating a complex system without having dependencies, thus avoiding DI? Also, I think it would make an interesting case study if you're willing to write it.
I don't think I did. Someone says, well if you use new, how are you going to unit test your class?
And one answer from a user of the language would be: Ya, I guess you'd need to change the way you're creating dependencies so they happen in the caller, and then you'd need to change your class so it takes the instance on its constructor, etc.
And this is the original OPs issue with C#, all the ceremony involved.
Another answer would be that the C# language designer could build a language level feature that avoids having to do that and allows mocking new inside a unit so it can be easily tested without shenanigans.
In fact, in Java-land, there is a library that lets you unit tests classes that use new, it's called Powermock. So it is feasible.
> #2 What stops you from creating a complex system without having dependencies, thus avoiding DI? Also, I think it would make an interesting case study if you're willing to write it.
I've moved to a functional language instead personally. That said, I do still like C# and Java, and think they are great languages. I'm also not against DI, or other enterprise patterns, some have legitimate uses. But I have definitely seen what the OP is complaining about, those languages are too deep in their own rabbit hole sometimes. To the point that half the developers don't know why they use DI, that's just part of the template they used to bootstrap their app. There's no thought process about, wait, what is the issue in my code structure and design that I'm facing, and what could I do to solve it. Instead it's just, I'm using all the "best practices", they are the best, and I'm using them all, all the time, so my code is the best it can be. Without any thought about what advantages they're even getting out of it.
That's why I asked about the "new". Most people don't really know the pros/cons. They only repeat what they read blindly: "New" bad bad, no use "new", never, very bad, antipattern, here link proof, must use DI instead.
And sometimes when they know of a cons, they don't think about tradeoffs against alternatives. They just say, well it has this one con, so it's very bad! An antipattern!
I think that creates a setting where you get monstrous framework and code bases, where people just throw every known pattern at it, no matter if it was called for or not.
> just they'll be hidden and tightly coupled
They're more hidden with DI than without, in my experience.
At any rate, having left the .NET stack around 5 years ago, I certainly don't miss DI. My current code has more and better tests than my C# code ever did, so DI didn't really help me there (nor did it hinder me-- it was neutral). But my current code is much more explicit, direct, and a fair bit more compact. I'm definitely happier with it.
To me, answer is clear - class A has obvious and loosely coupled ones. Lo and behold, class A comes from program which uses DI. Class B doesn't.
DIP is achieved precisely when the components (be they packages, projects, classes, types) are organized with the low-level modules depending on the high-level modules rather than vice versa. In English, this usually means the I/O and any heavy framework code is kept out of the program logic.
What DI does do is confuse this matter. Class relationships which are mere IMPLEMENTATION DETAILS of a module are elevated alongside the actual component architecture of the program. The object graph becomes obscured. Program execution order becomes nondeterministic. The callstack is completely ruined, undoing decades of enlightenment since Dijkstra's "GOTO statement considered harmful." And all my experience with the pattern proves, the DI container causes programmers to not even evaluate or understand whether their dependencies are even inverted, because all classes and their relationships just become one amorphous blob floating aimlessly inside the container.
Simply, this means that no implementation module, high or low, depends on any other implementation module, ever. It only ever depends on abstractions which are shared between modules.
I don't see how using a DI container can change the program execution order, unless one is misusing it terribly - after all, its sole purpose is to provide the correct concrete implementation of a dependency to an object at the time of its construction, the execution order from the perspective of the program is 100% preserved. Sure, maybe the container itself creates my graph in a non-deterministic way, but why would I care? If my program depends on this that's just bad design imo, no amount of libraries is going to save it :)
And the object graph is not obscured, in fact it is clarified, because you look at a class constructor and immediately can see what it's invariants are! And I have yet to come across a DI library that wouldn't immediately halt and catch fire if you introduced a circular dependency chain, so it's literally not possible to have these amorphous blobs (great expression though!) in any proper DI container.
+1. This simple idea is the basis of Clean Code, Hexagonal Architecture, Onion Architecture, Haskell, Functional-Core-Imperative-Shell, among other good architecture ideas.
If you're not using abstractions, you are not using the DIP. What is being described here with low-level modules depending on high-level modules is categorically not the DIP, and I am starting to wonder if this is perhaps part of the frustration that the poster we're replying to has experienced with DI, since turning your coupling upside down will have the same problems as just high coupling in general, except everything is upside down now and harder to read :)
I agree with your take on DIP.
We're using Microsoft Service Collection and with the Scrutor nuget component we were able to easily decorate an implementation from another package to extend the functionality adhering to the open closed principle OCP.
DI also enables your code to be open for extension but closed for modification.
Great stuff!
I am not sure what is a better way to solve this problem, but the original OP is right - it's far too much ceremony.
I think it is ok to depend on a DI-container in your unit tests for dependencies you are not directly testing, instead of stubs or manually instantiating them, to avoid updating unrelated unit tests when some class changes it's dependency somewhere in your project.
Instead I prefer system/integration tests where you test lets say a complete request from start to finish. Now your code base can change as much as you like, dependencies can be added or removed or classes or packages can be completely deleted, the tests stays the same. And now you are having tests that much closer matches reality instead of tons of mocks and stubs that can easily fool you to believing that they represent a good estimate of reality.
By doing this way a constructor based DI does not become a nuisance instead it helps you to organize your project, as it should be. Using a DI container makes it natural to break down your classes and automatically share the same decencies between them.
Counterargument to my argument is that if you don't use constructor based DI you can rewrite your class in anyway you like and the test stays the same, that is all true, however what DI gives you is the possibility to change the behavior of a class from the outside by injecting different dependencies, this makes your code better structured and easier to reuse.
If you want to accomplish that in the non-DI case you have to go thru trouble of configurable proxy classes which just introduces one more layer of bureaucracy, the thing you wanted to avoid by not doing DI.
My own criticism of DI containers is that they introduce magic to the project, personally I truly dislike code magic, however a DI container is nothing more than on the fly factory creator. In theory you could remove the DI container from a project by manually writing factories for each execution path, for me that becomes an acceptable level of magic as long as the DI container doesn't do lots of other things that factory couldn't solve.
Edit: misspelled convenience
You can still depend on abstractions without DI. Just use an interface or asbtract class in your code.
DI only plays into how you acquire yourself an instance of the concrete class for that abstraction you depend on.
So it can be given to you by your caller (DI). You can go fetch it somewhere (ServiceLocator). You can create it through a utility (Factory). Or you can create it yourself old fashion way with new.
> But you don't have to use a library; many times I'm writing a simple-ish console app and just use good old "Poor Man's DI" where I manually construct my object graphs at startup
I will support that. I'd like people to be precise in their criticism, do you find the DI pattern troublesome, or some particular DI framework?
I was kind of making the same point as you did, that you don't need to use any kind of DI library to achieve the DIP, but that in something like ASP.Net it is there and simple to use.
Outside of ASP.Net (console app for example) you can either do this manually, or you can get a ServiceCollection object from the framework, or you ca nuse a 3rd party DI library, etc.
Generally speaking if you don't configure this correctly you get a pretty simple message telling you that the container could not service the request for an IMyDependency or whatever and then it's pretty simple to figure out what you missed in the wire-up.
Edit: Also as others have noted, constructor injection is the way to do DI.
Duh! Then how’d you wire up interfaces with implementation aka constructor injection?
Construction injection is a type of DI, and it's optional, you don't have to use it.
edit: for clarity
That sounds like dogmatism, and I'm going to take a special note of the emotionally charged language you're using here; it's symptom of some of the underlying issues I see in our industry (more to be said on this).
That said, I do agree with the sentiment in general, but you can (and sometimes should) use the `new` keyword in your code, especially when writing tests. In some cases, it makes testing so much easier and helps with the maintainability.
"I hope you'll not reply-with saying "don't write unit tests"
After 2 decades in this profession, the only think I'm sure of is there is no "silver bullet". Of course, as a general rule, it's a good idea to have a test coverage of your codebase but sometimes it's not valuable and not worth adding it. So it's a "it depends" from me!
The main thing holding C# back is the culture around the DI and weird hostbuilder code associated with ASPNET. If you just skip that part, it's great. Unfortunately a lot of web examples, solutions, and code libraries themselves are ASPNET-focused, and more and more of the code becomes some truly bizarre, unreadable framework injection in an inside-out fluent builder.
Do you just want to `new SomeClass()` instead? If so, what's stopping you from just doing that?
When I think DI, I think: //Startup.cs has all the config "goo" I don't want to repeat elsewhere.
services.AddDbContext<MyDbContext>(o=>o.UseSqlServer(config.GetConnectionString("foo"));
services.AddTransient<MyService>();
//MyController.cs (or Page, etc) and others get to use it with no ceremony:
public MyController(MyService foo){ this.foo = foo; }
vs no DI, where the config "goo" has to be repeated in every place I intend to use it (or in this case it could also be buried in MyService.cs):
public MyController(){this.foo = new MyService(new MyDbContext(ConfigurationManager.GetConnectionString("foo")));}
public Dispose(){this.foo.Dispose();}
If I knew MyService would never be swapped out and only ever used in one place, I may opt for the no-DI route. I'll often start there then move it to DI when I want testability, reuse of config, plugability, etc..
The method could return a MyService, or an IMyService if you want to be more flexible. It can always create a fresh object, manage and return a singleton object, or manage a pool of objects. For the new and pool cases, you can have another static method for 'returning' the object when the controller/page is done with it, so you can manage disposal or pool availability.
The biggest advantage I see to this approach is in debugging: there is a clear stack trace back to the source of the object, and also to its implementation if you're not using an interface. With DI you often can't tell where an object came from, and sometimes it takes some digging to even find out its exact type (unless you only have one implementation of each interface, which is often the case.)
What you do instead is just:
public static IMyService myService;
public static IMyService getMyService() { if myService return myService; else if(isInTest()) return mockedMyService; else return new MyProdService(wtv); }
And call that in your controller that needs to use MyService.
This is different in that it's static. You don't add a service to it, it creates it the first time you ask for it (if singleton), or everytime you ask for it (otherwise), or pools it, etc.
I recon some similarities, but most enterprise "service locator" involve a lot more than this, and often are meant to give you this runtime dynamism where you can load and unload services as the app is running, etc. making them way more complex for apps that don't care about this.
var service = Startup.getMyService();
And potentially know if the controller should dispose service.
This doesn't seem much less "in the way" or "massive" than:
services.AddSingleton<IMyService,MyService>();
No registration or configuration is needed, because the types are hard coded. If you need mocking support, a single IsTest config setting could be used in a conditional statement to determine which implementation to return.
That's very little additional coding if you use a ternary statement, and it lets you opt-in to the mocking mechanism if/when/where you need it, instead of permiating your entire architecture with it.
I find it very useful for several purposes that have nothing to do with mocking or testing.
> and have become blind to just how much extra code it generates
It doesn’t generate any extra code at all.
For the record, I think that built-in .NET DI is pretty bad. It would be better if they managed to avoid including that abstraction in the framework, even if I personally choose to use DI in most of my projects.
I’ve also used Autofac in projects where it was already in place - I don’t hate it but I like DryIoc better.
For our multi-tiered system, this means managing caching and logging of certain calls and events at the level of data access. And at the service level, it means dealing with things like authorisation.
Smart use of DI sets the system up in a way were it's easy to handle these types of cross-cutting concerns where appropriate, and with minimal boilerplate. It also simplifies refactoring.
I recently ran through the entire ecosystem thinking that there must be something akin to Sinatra (micro web framework) in .Net. Nope. Every single thing is built on top of ASP .net.
I did find a WIP library where they are trying to go for a low ceremony framework https://github.com/featherhttp/framework, but even that is built on top of ASP .net
For me the worst bit about .Net is their treatment of F#. IMO, it is one of the most practical and usable functional languages out there. But none of the official docs have anything on F# at all. Here is an example doc; see if you can find any code example for F#. https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor...
Its always C#. And I have 0 interest in that kitchen sink language.
Between ASP.Net and C# being the entire world for .Net, I have really not much interest in using it for anything. There are plentiful of interesting languages and ecosystems that I can use for my small time projects.
Here is the last techempower for it; https://www.techempower.com/benchmarks/#section=data-r19&hw=...
1. For a new comer to the language, this is still too much of a hurdle. 2. If I wanted to use F# only as a proxy for C#, then I would rather use C#. I want to write F# code in a functional first and idiomatic way.
This is the stock answer I have gotten in the .Net Community and as someone who wants to use F# for web apps, its a huge turn off. There are pretty much no tutorials, no recent articles and no performant native F# libraries out there. There is no push from MS to improve the situation. and the community is like - This is fine!
Expecting a new user to write c# code with "let" isn't going to fix that.
Giraffe [1] is the de facto standard F# web framework. It's a plugin on top of ASP.NET (for all the reasons described elsewhere - performance, security, ecosystem) that lets you write functional-style 'route >=> function' code without controllers.
If you want a more batteries-included approach, Saturn [2] builds on top of Giraffe to provide, well, more backend batteries (database layer, etc.)
Finally, the SAFE stack [3] are a set of project templates that combine the above with a F# frontend (via the Fable JS compiler) and some Azure deployment helpers.
[1] https://giraffe.wiki/docs
[2] https://saturnframework.org
[3] https://safe-stack.github.io/
Also, F# is community owned to my understanding (up to the statement of a MS Research Employee and F# inventor Don Syme to say that the "Microsoft." prefix on ASP.NET Core are not open source community friendly) while C# is 100% design & developed by MS.
Which - IMHO - makes it very delicate for Microsoft to communicate well here.
More F# documentation and support from MS would be great, but IMHO F# examples in the BCL documentation would not be the best use of resources.
And for sure, they are all based on ASP.NET Core. ASP.NET Core is extremely modular which allows, e.g. a middleware stack like Sinatra being built on top of ASP.NET Core's Kestrel webserver (which ... cite once again Techempower benchmarks) beats everything nearby in the current situation. It would be a horrible choice to built a web server framework right now on .NET without using Kestrel. You will never achieve to get close to the performance of that.
Not very likely, but I think separating the two out would help the ecosystem a lot in the long run.
Look at the techempower benchmarks implementation .. they run as close as possible to Kestrel. They have variants with raw kestrel interface, some with low-level middleware (I guess with express like routing) and then full-blown MVC like comfort.
If your view of low ceremony is matching HTTP verbs to methods, you may happily code your own against the HTTP primitives; if you want a framework that abstracts the complexity and provides a shiny-happy path, you may have to allow for some ceremony. The tooling is the antidote to this.
I think the place you trip up here is in not wanting to use a batteries included approach, while using a framework that makes the necessary abstraction/complexity tradeoffs for projects of mid to large scale. The appropriate tradeoffs _cannot_ be one size fits all.
F#'s support is tricky, as the number of users here is dwarfed by those in C#; and although .NET supports this language diversity, there are no direct idiomatic 'conversions' possible between C# and F# in many cases. This multiplies the effort of providing documentation and examples. I too would like to see it treated as a first class citizen, and for that - the code is OSS.
You have hit the core of my issue here. I am more used to the node-express or golang's way of working where you start off with the smallest and simplest scaffolding and then add on the bits that you need. Rails and ASP .Net are not something I want to use for my small time projects.
Its probably unreasonable for me to expect .Net to be something completely different from what it really is, but I really hope that .Net grows up to enable other way of using it.
You can trim out a lot. Take a look at the techempower benchmarks to see examples of different usage of the framework.
This is a dangerous way to think about complexity management.
Does it? The frameworks I use today are really, really simple. Polka on Node and Go + a few helper libraries. It's all really simple and clear what's going on, and I can solve problems of just about any complexity I've come across with just those basic tools.
As each of these orthogonal concerns starts drifting in, applications become more and more complex as they 'tack on' support for these in code, rather than having structures in place to allow for extensions and modifications.
Abstractions always have their cost. Complexity exists in the problem domain of the web, and always will. .NET Core is generally well architected in response to a long experience in enterprise and developer concerns. It's not a magical answer to every domain's requirements, but it's generally a damn good compromise ime.
.NET supports as much ceremony as you want to deal with.
This is only true in theory, but couldn't be further away in reality.
Microsoft suffocates everything which is not built in-house 1), so there is absolutely no OSS ecosystem going on in .NET which would allow a choice of different non-ASP.NET web frameworks.
Other languages have a wealth of different web frameworks and some are super basic and lightweight and others are huge with batteries included.
You don't get that in .NET at all. Everything is ASP.NET Core, and ASP.NET Core is a typical Microsoft product - an attempt to build a one size fits all solution, the magic silver bullet to solve everyone's problems at once. Like with all silver bullets, when trying to please everybody you end up pleasing nobody.
1) Except F#. In F# you will find a more vibrant OSS community and non ASP.NET alternatives (most famously Suave), but only because Microsoft doesn't care about F#. Instead of creating an equal level playing field for C# and F# they are morphing C# into an F# hybrid and eventually will stop caring about F# even more than now.
It's probably a cover your ass situation, especially at the larger, more conservative, enterprise-y, non-tech companies .NET is frequently used at. No middle manager would get fired for suggesting to use a Microsoft developed & endorsed solution instead of using some hipster open source framework.
ASP.NET is not a single monolithic thing, the Core evolution has made everything modular and you can plug and play the parts you want. You can use just routing to translate URLs to endpoints, or just the view engine, or just the serialization bindings, or just razor pages, or just MVC, or just low-level HTTP responses, or just GRPC, or anything else. The entire pipeline is a series of middleware, which itself layered on the Kestrel webserver, which itself is layered on low-level sockets wrapped with Pipelines and Memory APIs.
There is no one-size fits all, and asp.net explicitly recognizes and solves for it. The issue might be your expectation that there is a small/light and large/heavy framework. There isn't, you just make one to fit you.
For ex, AFAIK, there was no community participation on DI Design and community feedback on DI design was rejected. Then it was made the default in ASP.NET.
https://blog.simpleinjector.org/2016/06/whats-wrong-with-the...
I agree that the ASP.NET documentation is lacking in F# code samples (although the example you picked is a bad one as Razor doesn't support F#, and so naturally wouldn't have F# code samples). You might find that Giraffe is a more comfortable framework, as it's essentially an F#-ified shim on top of ASP.NET.
To me it's usually a synonym to the full-fat MVC with controllers, DI, everything. This is reinforced by most of the dotnet templates and documentation, which make it feel like if you ask for a banana you're given a gorilla and the entire forest whether you want it or not.
In comparison, if you look at Go you'll see the "net/http" package. It's the HTTP server. Extendable. Built in - not something to download from NuGet. Only does HTTP - not reinventing its own DI, configuration management, IIS, etc.
That said, the new C# language features really do reduce boilerplate when used judiciously - multiple return is now available, "expression-bodied properties", improved template strings etc. Lots of good stuff that is hard to find good documentation for :)
As far as starting up F#, I try some of the resources here https://fsharp.org/
If you want to build apps in an entirely different way that looking nothing like the wretched enterprisey nonsense of the C# and Java worlds, check out SAFE: https://safe-stack.github.io/
I have been a .Net developer for about 15 years and remember when it was a violation of terms of service to publish .Net performance measurements. Especially performance comparison against Java was something that would get you on Microsoft's bad side real quick.
So it's really I think the difference between performance being a secret, and it being on the front page and Microsoft throwing significant developer resources into it.
My point being, I thought .NETs performance was pretty well regarded already fwiw.
edit: here you go, ranking of web frameworks: https://www.techempower.com/benchmarks/#section=data-r19&hw=...
ASP.NET Core 3.1 holds up pretty well, only beaten by C++, Rust, and a Java/Kotlin Framework called Jooby
And yes, beating PHP was never a thing. Beating node and Java containers is a thing.
i feel like a lot of people’s ideas about c# performance are from before .net framework 4 which was years and years ago
This benchmark ranking shows ASP.NET Core coming in 6th place, compared to Spring at 34th, Express at 76th, and Django/Rails near the bottom.
I can't speak to the usefulness of the benchmark used.
1: Drogon (c++ Framework)
2: Actix (Rust Framework)
3: may-minihttp (Rust Framework)
4: Lithium (C++ Framework)
5: Jooby.x (Java/Kotlin Framework)
6: asp.net core
So I guess that would be .NET Core 3.1
However, also check the definition of the rows. With Plaintext there are 3-10 entries for ASP.NET Core with different level of comfort (from "do not do that at home", to express like middlewares to "rails like MVC")
You even have hardware intrinsics available to play with, and for performance-sensitive libraries such as hashing algorithms, it's typical for performance to come within spitting difference of RAM speed.
Where on Earth? I could tell you precisely where if it mattered. I'm certainly glad the team at MS has been focusing on performance now because they certainly didn't when the team I was on decided to try it. (Hey, maybe they could help out the Visual Studio team as it takes like 10-15 seconds to open the app to an empty screen).
I haven't run any tests lately (and probably never will as I'm not interested in a language that emits IL rather than machine code; or a language that uses a GC) so I apologize for throwing out the 15x number when it is probably not correct. It won't happen again.
I would wonder why some game studios (like Supergiant Games / Hades) ended up porting their C# game engine to C this year? Was it because C# was fast enough? Or maybe it was to make the game more portable to other platforms? Not sure.
I haven't come across a package that isn't available for .NET Core in at least a couple of years, I think.
.NET Core in Version 1.0 cheated then by just taking the network layer of node.js called libuv and linked it up (C -> .NET) . In Version 2.x they wrote and independent socket based networking layer straight in C# which outperformed libuv significantly. In Version 3.x they switched over to use new low level runtime primitives (e.g. Span<T>) which could never have been delivered in the .NET Framework (no breaking changes!). Now in .NET 5 more runtime improvements were added.
I think what really changed was the mentality. Before low level stuff was tried to be contained and used mostly for interop. Now the value of the performance of safe low level primitives is seen.
[1] not web stuff, "plain" C# essentially. It has some compute-bound things like encryption and compression and some fancy tree structures, uses a lot of linq in certain parts.
I remember trying it out (on Linux, that is), and really liked the concept, but it was painfully slow, so it was no fun for me. Like, waiting 3-5 seconds in the REPL for a single line to compile and execute.
Kill the bitch and you will have normal times.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
But unlike GO, you will need to write 50x less lines of code to achieve the same result.
I think the fact that Go compiles to native executables as the default, just gives the impression that it is "low level". That and the lack of features I suppose.
I say all of this as someone who loves Go as a language by the way. C# could use some simplification!
I looked at the benchmarks.
In cases where .NET is faster it's because the C# version is optimized using unsafe code and x86-specific (i.e. non-portable) AVX2 intrinsics to do the math.
Go version is written in straight-forward way. I think all Go programs are shorter than their C# version.
Compare mandelbrot benchmark:
* https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
* https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Those are not fair comparisons and therefore don't paint the correct picture of relative performance.
This is the link you should share: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://medium.com/servicetitan-engineering/go-vs-c-part-3-c... - check out "Runtime Performance" section here, it shows that geometric mean on exactly this benchmark is heavily in favour to .NET, and it's .NET 3.1, not .NET 5.
Go and C# are so similar that for benchmarks you can transliterate one into the other.
We should be comparing programs that are comparable.
My point is that BenchmarksGame is not comparing comparable programs.
Code for some languages has extreme optimization, including doing things that most people don't do in day-to-day programming.
Go supports assembly. Given enough time I could probably implement a given benchmark in assembly, beating C# (and pretty much anything).
This is used to good effect in Go runtime and some really niche applications but in real life I don't have infinite amount of time to micro-optimize my code and write parts of it in assembly.
What I want to know is the performance of competently written code.
Is this "competently written" ?
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
And we have an unsubstantiated claim from you, the Go is faster, and code is shorter, on average. What is this based on?
What to you is a fair comparison?
Presumably unsafe code is not allowed for C# and Go assembly not allowed for Go. Are there any other restrictions? We could do a naive port of one of the Go benchmarks to C# and see how it goes, which one do you think is a good candidate?
I wrote a lot of C# and a lot of Go. C# has so much more ceremony that it is more verbose and it shows even in the code we're discussing. You just have to be willing to look.
Fair comparison between C# and Go is actually very easy. Those languages are so similar that you can transliterate a given benchmark from one language into another. Then benchmark those versions.
There's just no way that on an average program Go (language statically compiled to assembly with a very competent code generator) will loose in performance to C# (which compiles to bytecode and then JITs that code at runtime using a much weaker code generator).
The Go mandelbrot program you looked at is a little larger than one of the C# mandlebrot programs.
816 C# .NET #9 program
894 Go #3 program
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Go is well known for having laborious error handling (prompted by a lack of exceptions) and masses of boilerplate and verbosity precipitated by the lack of various other features, the most glaring of which is an absence of generics. This means implementing custom data structures is generally an exercise in copy paste, or ignoring type safety.
Things like the lack of extension methods, JSON and YAML serialisers that require explicit annotations everywhere instead of conventions, the lack of OO/polymorphism, no ternary operators, null chaining operators, etc. etc. also contribute to the verbosity.
Simplicity is good, but the lack of expressivity and the need for such boilerplate is bad. A pretty poor type system makes it worse. Go has many strengths, but I massively prefer C# for the majority of non-trivial situations.
Regarding performance, it depends heavily on what you're doing. For example, Go's allocator and GC is designed to optimise for low pause latency, whereas dotnet is optimised more for high throughput and good cache coherency. Given the extent to which most apps are stalled waiting for memory, the lack of good cache coherence in Go allocations, and the lack of a generational GC can absolutely decimate performance. DotNet is capable of allocating at 25x the speed of Go, for example.
Go read https://medium.com/servicetitan-engineering/go-vs-c-part-3-c...
1. First, unsafe doesn't mean you shouldn't use it on .NET. It just means you need to do more checks manually.
2. A lot of things you can do in Go are unsafe in .NET terms - e.g. even slice is a leaky & unsafe abstraction: https://alexyakunin.medium.com/slice-an-extremely-leaky-abst...
3. Yes, the fact Go doesn't support SIMD intrinsics explains why it loses not only to C#, but also to C++ and Rust on math-intensive tests. But it loses to C# on other tests too - e.g.: - https://benchmarksgame-team.pages.debian.net/benchmarksgame/... - https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
And as you might notice, the code length doesn't differ much there.
Go has support for assembly and supports SIMD intrinsics just fine. See for example https://github.com/bamiaux/rez/blob/master/vscalers_amd64.s
My point is that if you want to KNOW which language is faster (as opposed to trying to PROVE that YOUR language is faster) you wouldn't compare a C# code optimized with SIMD intrinsics with Go code that doesn't use SIMD intrinsics.
The problem with BenchmarksGame is that it doesn't try to enforce apples-to-apples benchmarks.
It's fun thing to see how far you can push a given implementation if you're willing to spend a lot of time on it.
It compares implementation of the benchmark code, not the quality of the compilers on the code that you'll actually write in real life.
Sorry, I missed the proof - can you point me to it?
What you presented as "more verbose code" isn't actually a proof - i.e. yes, SIMD code is obviously more verbose than a normal one. And faster.
> Go has support for assembly and supports SIMD intrinsics just fine.
But wait, in this sense any language has support for assembly and SIMD. Bundled assembler is not the same as language-level support for SIMD - and even in https://benchmarksgame-team.pages.debian.net/benchmarksgame/... a large portion of SIMD code is actually cross-platform (what uses Vector<double>), and I am pretty sure sticking to just cross-platform SIMD APIs would be enough to beat Go.
> You wouldn't compare a C# code optimized with SIMD intrinsics with Go code that doesn't use SIMD intrinsics.
You use what's not against the rules, and it's not against the rules on CLBG. You're free to submit your own version of the same benchmark on Go relying on SIMD or whatever you prefer.
> It compares implementation of the benchmark code, not the quality of the compilers on the code that you'll actually write in real life.
Yes, any benchmark is somewhat biased. But honestly, comparing real-life benchmarks is even harder - they involve much more components, so whoever isn't happy with the results can always claim it's a comparison of frameworks, not the actual programs, etc., etc.
In cases where we want to store really complex object graphs in SQL, we typically resort to JSON serialization of those objects. This allows for us to keep the important SQL facts in dedicated columns (i.e. for PK/constraints/indexing) and also have a copy of the full serialized POCO sitting in the last column for convenience.
For reference, these databases (SQLite) are not shared with external parties/systems so we can get away with this sort of heavy-handed denormalization approach. Our use cases of the databases are very well bounded.
In terms of migrations, we just write a simple for loop and use SQLite's user_version pragma to keep track of an incrementing integer version. This means that we actually have a superior solution to EF in that we don't need additional special metadata tables to keep track of this information.
If the biggest problem is whitespace/casing in your SQL, you are probably sitting in a really good position. This is something we do try to be strict about. All of our SQL lives as string constants in 1 file per database so it is really easy to keep track of what is being used throughout in a consistent way. We do not permit developers to write SQL strings outside of these files. It all has to be in 1 place. We have 1 file for all SQL commands that will ever be used against that database, and a 2nd file for its schema migration scripts.
this can of course depend on workload and whether you care about startup time or throughput vs tail latency etc, but on the whole it tended to allow you beat out java, go and other competitors most of the time.
with AOT being a standard option now, getting quick startup time is easier too, along with the other improvements
My hope was that by the time .NET 5 was out of preview, we would have seen something for this so people could write C# for Canvas in WebAssembly. Alas, not yet!
https://github.com/BlazorExtensions/Canvas
It supports both Canvas2D and WebGL. The API supports batching calls across the interop barrier, so that the interop performance costs are minimized. As for Blazor being slow: please correct me if I'm wrong, but my understanding, that Blazor code is interpreted, because JIT compilation in wasm is not really possible. The implementation of AoT compilation, which will fix the perf issues is coming along, but it's not quite there yet.
There are some interesting and insightful comments even in those under this article though. I find hiding the entire tree once I see a comment that’s just there for the sake of being negative makes it easier to find the valuable ones.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://salsa.debian.org/benchmarksgame-team/benchmarksgame/...
README
https://salsa.debian.org/benchmarksgame-team/benchmarksgame/...
Personal wish for author (Alex Yakunin) - now compare it with rest (Java mainly).
As for me, I wrote https://medium.com/servicetitan-engineering/go-vs-c-part-3-c... that compares C# and Go, and conceptually nothing changed a lot from the moment this was written.
IMO Go is the only viable competitor to .NET nowadays. Java is holding there solely because of Android - performance-wise it's so far behind...
DI == Dependency Injection
Is it basically LiveView on Elixir?