Hacker News new | ask | show | jobs
by uticus 1483 days ago
> Thoughts on modern C# and .NET

Pros:

- Excellent documentation and widely used (not just modern btw, MS documentation is world-class and has been for the ~8 years I've been exposed to .NET API documentation)

- Excellent code structure, supports very large/enterprise type code bases

- Mostly sane improvements, with regular rollouts

- Now cross-platform (although ref [0] for what it's like to develop in a non-Windows environment)

- IDE support is 5-star. MS own IDE is at least 4 star, and Rider (JetBrains) seems to be very popular as well. I've used three versions (pre-2019, 2019, and now 2022) at different paid levels, no big gripes. You get great debugging, profiling, REPL, etc.

- Mostly opinion/experience: Allows for a range of styles, while IMHO balancing correctly with consistency that is easy to pick up and collaborate with. It is possible to write confusing code, but not as much as other languages (ref [1]).

Cons:

- It is cross-platform according to the docs, but in reality it comes from and is relied on mostly in a Windows world.

- Testability - if you like unit testing, you will need to make everything testable also exposable - if not publicly, at least within the same assembly.

> If you have experience with ASP.NET Core...

Yes. It's pretty good. Apart from a question specific to the language or framework, you will need to quickly become familiar with how stuff works behind the scenes. It might also be beneficial to look into how deployment works, depending on your target (IIS, Azure, AWS, etc).

> Did you enjoy developing...

Yes, for larger projects that need collaboration. Otherwise I use Ruby.

[0]: https://news.ycombinator.com/item?id=31520399 [1]: https://www.ioccc.org/

* edited for formatting

4 comments

Quibbles:

- Documentation: is ALL over the place. I do not find it excellent. There is LOTS of documentation but due to naming (.NET framework != .NET Core != .NET Standard != .NET [current]) you can find solutions that just don't work in the version you're in. Blogs are often the best source of details, but you have to have great search-foo to find the right one, and like stack overflow these won't age well (especially with the new rapid release cycle). There is documentation, and lots of it. *Warning*: On the MS official docs you can set the .NET version (with a drop down in the top left).. however if you try and visit something outside of that version, it will take you there and silently switch you to the last version where it was valid.

- Improvements: are great, they see what's happening in other languages and incorporate it. .NET might not be the first to have (although sometimes it is: `async`) but eventually gets the best solution to a problem (`Span` is rather like `slice` in golang).

- Cross platform: is very strong. Doing it the cross platform way is completely subject to the documentation trap above. A lot of the docs out there are for .NET Framework (the OG, which is not cross-platform) so you start off down the wrong path not realizing there's a cross platform way. This compounds pretty quickly.. if you're building cross platform, setup automated cross platform CI early.

- Testability: It seems you have not discovered `InternalsVisibleTo`[0], available as both a class-decorator or a project assembly attribute. It's been around since .NET Framework 2.0 (2005). Very much like the C++ `friend` declaration. You can allow a test assembly (only) to see private/protected/internal pieces of your assembly, if you desire.

- .NET (formerly .NET Core): Is great. Faster, leaner, and their future. Subject to the documentation trap above. Because of their rapid releases (and good comparability guarantees not forcing upgrades), it can be really hard to find good examples/docs of the newer releases.

[0]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.c...

" It is cross-platform according to the docs, but in reality it comes from and is relied on mostly in a Windows world."

Not my experience at all, everything I do at work is c# on containers running Linux.

Just on your point on Testability, I don’t believe this is the issue you make it out to be: my standard approach is to make all the types/methods I need to test ‘internal’ and then expose them to my unit test assembly using this assembly level attribute, https://docs.microsoft.com/en-us/dotnet/api/system.runtime.c...
I don't understand this logic. Test things that are public. You made them public for a reason. Those are what your class says it will do. Confirm it does what it says it will do and you're done with testing.

I'm not sure I've ever used the "internal" keyword in 12+ years of C#. Assemblies are about deployment; public/private is about the contract your code is agreeing to. They have nothing to do with each other, so internal is an inappropriate conflation of concepts, by definition.

Because all of our projects use Microsoft.Extensions.DependencyInjection, I make all our service classes internal

The way other projects can access these services is by using the project's helper to register them all in the DI container by their interface. That way I can be sure no one is directly using the classes and make breaking changes to the constructors (like swapping loggers or api clients), but still access them from tests.

And if you have two different DLLs that offer this pattern, but they overlap in the interfaces they register? The downstream consumer has no way of saying "I want my IFoo to be a FooA from assembly A, and my IBar to be a BarB from assembly B". And this may depend on a configuration setting! Real example: we have a bunch of useful Azure functionality in one DLL and a bunch of general useful web functionality in another; they implement many common interfaces and are alternative services you can pick depending on your deployment. But it's not as simple as "when on Azure, pick all the Azure stuff" -- it depends on which Azure services you've configured for that individual project.

If they overwrite each other, you might be able to get away with calling the god-registration-functions in the correct order (which you can determine only by reading the source code of the two functions, which are probably in different solutions), but if you really have a well mixed set of needs from A and B, this is probably not possible.

So you say: "okay, well, in this case maybe those concrete services should be public instead of internal. Obviously in this case we'd go back and do that." So your logic is: use "internal", except when it turns out we shouldn't have used it, then don't. So what purpose was it serving to begin with? None. The last lesson is that if you find yourself saying "in this case", you're actually talking about every case. That is to say: "special cases" are a symptom of a weak mental model. Good models don't have special cases.

So indeed we've identified several ways to improve, all stemming from the fact that you are using the "internal" keyword!

Wow, there really are people who just drive-by downvote anything that doesn't a-priori agree with their cargo cult mentality instead of stopping to think about it for 30 seconds? Of course they can't leave a comment, because that would require them to actually consider my point, which would put them in danger of actually learning something.

Downvoter, the problem with your downvote is that I am correct. DLL dependency/deployment is a completely separate concern from code contracts and visibility; therefore "internal" is always a mistake and should never have been added to the language. If you use "internal", we've identified that you have gaps in your understanding of software development that you have the opportunity to fill. But instead, go ahead and just downvote me again without comment. That's a lot easier than improving.

> if you like unit testing, you will need to make everything testable also exposable - if not publicly, at least within the same assembly.

One of us is doing testing very wrong then. Maybe I don't like "unit testing", if that means reaching into the internals of every class. Just test that your code is following the contract it says it's following.

The idea that you need to test private methods by running them out of context sounds to me like someone saying "A downside of this language is that you can't jump into the middle of a function to test individual lines of code. You can only run functions from the beginning." Yikes!

> Just test that your code is following the contract it says it's following

Usually testing output from a contract implementation works just fine. But sometimes my implementation contains bits abstracting parts of the whole. Those bits needs to be tested too, even though from an outside perspective there's no need to expose each bit. The consumer is only interested in the whole result.

A consumer of the contract may want to pass in a Dog object, and get a result that tells if it (the dog not the consumer) has fleas. My implementation will result in true or false, but I'd like to abstract and separately test the parts that checks if the dog itches, if it has tiny animals flying in a cloud around its head, etc.

I like good sane code coverage, but I also want it to be clear what is expected to be publicly consumed to drive the business logic, and what's just me making things tidy behind the scenes.

How did I end up in another conversation about unit testing?

Why test that seperately? You should be able to test any code from the public API.

Otherwise that code is unreachable and should be deleted.