4 ms·
Modern C# is way too complicated, and I really dislike that. On the other hand, as I've tested out the new features, I have to admit that most of them are good.
by kg 5y ago
Modern C# is way too complicated, and I really dislike that. On the other hand, as I've tested out the new features, I have to admit that most of them are good.
Part of what makes this work is that wherever possible, they made the features compiler-only, which means I can use them without having to update my application to use a newer runtime or newer libraries. This means I don't have to worry that updating my code will break compatibility for existing users.
The other key thing for me has been realizing that a significant portion of the new features enable me to replace old verbose/bad code with new concise code that has desirable properties - it's become easier to reduce duplication and easier to write efficient code that avoids allocations and better exploits the type system.
Some simple examples:
the addition of 'in' parameters which behave like 'ref' but are read-only, which enables the compiler to automatically pass values by-ref when appropriate - this means you can replace existing by-value arguments with by-ref arguments and not break any existing code. It's really great.
they added 'ref' locals and 'ref' return values, which means you can do a clean and efficient mutable version of 'list[i]' like in C++ but without any of the semantic issues (storing the ref is explicit and the compiler prevents you from accidentally introducing memory safety issues.)
generics were expanded to allow you to safely manipulate pointers, which means that high-performance code no longer has to use gross tricks and can now be type-safe. in modern releases you can now finally do arithmetic with generic types as well (though sadly this requires an updated runtime).
The addition of tuple types bothered me a lot too until I realized that the tuples were silently updated to value types, which means writing obvious tuple-based code is actually highly efficient and I don't need to hand-write record types.
the async/await features have some major downsides, but on the other hand the compiler and library design teams built in a bunch of really wise escape hatches to let you work around issues. The whole state machine can be customized to swap out all of the internals, you can define your own types with seamless 'await' support, and the compiler will (in release mode) aggressively turn the state machines into structs so there aren't any allocations. It's really nice and transitioning my code to it has been a huge improvement.
linq is notorious for bad performance, but they made it possible to provide your own implementation of all the query operators so it was possible for me to define my own methods and make my linq queries not allocate at runtime. really nice (though it comes with its own tradeoffs).
- kreutzwj 5y ago> they made it possible to provide your own implementation of all the query operators so it was possible for me to define my own methods and make my linq queries not allocate at runtime. really nice (though it comes with its own tradeoffs). I would like to know more about this, would be really useful. So you have any recommended docs to read?
- kevingadd 5y agoI don't have a bookmarked page about this, but essentially if you implement your own version of the extension methods from the Linq namespace, the compiler will prefer those over the IEnumerable<T> extension methods. If you want to debug this a great way to do it is to examine the generated binary in a decompiler and you should see which methods the compiler decided to use. The async/await support also works this way - it will just look for methods with an appropriate signature and try to use them if possible: https://devblogs.microsoft.com/pfxteam/await-anything/ https://devblogs.microsoft.com/pfxteam/await-anything/ For both of the above examples, it's sufficient to write extension methods that target a specific type, so you don't have to change the actual types you want to query or await - just provide extension methods. This means you can do it for types you didn't write, as well. For more advanced scenarios, you can write a query provider that consumes expression trees, and the compiler will generate an expression tree for your library to consume instead of calling a bunch of methods: https://docs.microsoft.com/en-us/archive/blogs/mattwar/linq-building-an-iqueryable-provider-part-i https://docs.microsoft.com/en-us/archive/blogs/mattwar/linq-...