Cleaner DI only constructors #2211
-
Ever since DI became a standard practice, I've been writing and reading more constructors that are dedicated to DI.
And if you only have one dependency, you can use an expression body to get something super tight, like this
However, once you add another dependency, you're forced to go back to the much more verbose
I was wondering if there were any plans for a terser, sugary version of this? Maybe something that builds on expression bodies, but strictly for constructors?
Or echo the Records proposal
I think adding this would help more immediately distinguish strictly DI constructors, and ones that perform some other operations.
Or potentially
|
Beta Was this translation helpful? Give feedback.
Replies: 15 comments
-
To be clear, I'm not asking the property / field declarations be inferred like records would be |
Beta Was this translation helpful? Give feedback.
-
Tuple deconstruction works, and the compiler optimizes it away. public GameRenderer(IGameGrid gameGrid, IScreenBuilder screenBuilder)
=> (_gameGrid, _screenBuilder) = (gameGrid, screenBuilder); |
Beta Was this translation helpful? Give feedback.
-
I hadn't considered that. Thanks! Is this a pattern you've seen used before? |
Beta Was this translation helpful? Give feedback.
-
Not personally, but it came up on the Roslyn repo as someone wanted to know why the C# compiler wasn't optimizing away the creation and deconstruction of the tuple in C# 7.0, and the team decided to add that optimization in one of the minor releases. |
Beta Was this translation helpful? Give feedback.
-
I've seen Jon Skeet talk about liking this pattern in his recent talks. I think I'd only use it if I wanted an expression body. |
Beta Was this translation helpful? Give feedback.
-
Yeah, and I would certainly not use it beyond maybe three/four parameters/fields as it would become visually cluttered and more difficult to correlate which is assigned to which. It's a neat trick, but I don't think it offers much to the developer unless they have a curly brace allergy. |
Beta Was this translation helpful? Give feedback.
-
Blowing my own trumpet here, but as far as I know, I was the first person to propose this pattern. I had a big smile on my face the day Jon Skeet approved it. I use it a lot as I'm a big fan of single line methods and so use expression bodies a lot. And I do indeed have a curly brace allergy 😃. Using them with constructors just made sense to me. Since the team added a compiler optimisation for this, I took it as them approving its use too. I completely agree with @HaloFour though re the parameter count. I've tried it on many parameters before and it certainly does get ugly if used for more than 3-4 of them. So I limit its use to around that point. |
Beta Was this translation helpful? Give feedback.
-
How about this? public class GameRenderer {
private IScreenBuilder _screenBuilder;
private IGameGrid _gameGrid;
public GameRenderer(_screenBuilder, _gameGrid);
} Tight, clean, sugary goodness that pushes the DI parameters straight to the fields. You could even replace the semicolon with a set of braces if you needed to tack on extra functionality after the DI assignments! ;-) What say, all? Is this a syntactic compression too far? Is there a syntactic ambiguity I'm missing here? |
Beta Was this translation helpful? Give feedback.
-
More a case that you haven't gone far enough, as in records, public class GameRenderer(IScreenBuilder screenBuilder, IGameGrid gameGrid); 😃 |
Beta Was this translation helpful? Give feedback.
-
Ah, I hadn't seen the Records proposal. Now that you've pointed it out, I see where you're going with it. However, I don't see a lot of overlap between Records (which are generally simple bags of public state with no logic -- DTOs, generally) and DI-consuming classes (which privately consume the injected dependencies to perform encapsulated logic). I could see a solid case for something like: public class GameRenderer {
public GameRenderer(private IScreenBuilder _screenBuilder, private IGameGrid _gameGrid);
} where the |
Beta Was this translation helpful? Give feedback.
-
It would be worth reviewing the conversation around Primary Constructors. At one point they were intended for C#6 (beta releases of VS2015 included support, IIRC) but they eventually floundered on some rocky problems. Here's one good starting point: dotnet/roslyn#6997 |
Beta Was this translation helpful? Give feedback.
-
I've always envisioned "sweetening" the DI pattern with a new field modifier, let's call it public class GameRenderer
{
injected IGameGrid _gameGrid;
injected IScreenBuilder _screenBuilder;
} Behavior:
You could always enhance/override the default ctor: public GameRenderer(IGameGrid gameGrid, IScreenBuilder screenBuilder) : this(gameGrid, screenBuilder)
{
// do more
} Or, maybe there's a shortcut for that: public GameRenderer(IGameGrid gameGrid, IScreenBuilder screenBuilder) : default
{
// do more
} Thoughts? |
Beta Was this translation helpful? Give feedback.
-
Actually, it's possible that primary constructors would cover my above proposal just as elegantly (or more so). I missed that. |
Beta Was this translation helpful? Give feedback.
-
Just in case you weren't aware, Visual Studio will generate a constructor from a set of fields: and also generate fields from constructor parameters: which you can then follow up with null-checks: (... there's also "add null checks to all parameters" if you've got multiple nullable parameters) I don't think I've ever spent more than a few seconds going from fields -> constructor, or constructor parameters -> fields. I'm not sure that adding extra syntax to save a couple of keystrokes is worth it. |
Beta Was this translation helpful? Give feedback.
-
@canton7 That argument could be made about most new language features since 1.0. (Why do we need automatic properties if an IDE can just generate the long-hand version, for example.) Terseness is more about readability than writeability, and a code generator doesn't address that. It might seem minor but small enhancements that express intent more cleanly and with less noise really add up. It's why most devs prefer C# over VB. It's why some prefer F# over C#. It's been a primary motivation behind inventing entirely new programming languages. |
Beta Was this translation helpful? Give feedback.
Tuple deconstruction works, and the compiler optimizes it away.