-
-
Notifications
You must be signed in to change notification settings - Fork 837
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for required properties #1364
Conversation
…full set of parameters provided for the constructor, so required properties behave a lot more like any other constructor argument. Also, cache the set of properties to write to, so we don't have to call GetRuntimeProperties() every time.
I'll try to review this soon. Something that occurs to me - if it's a major semver increment (which I agree with), are there other breaks or things to mark |
Only one I know about off the top of my head is the RegisterGenericDecorator one, which is #1297 |
…t files not being generated in the right folder.
Codecov ReportBase: 78.02% // Head: 78.22% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## develop #1364 +/- ##
===========================================
+ Coverage 78.02% 78.22% +0.20%
===========================================
Files 195 197 +2
Lines 5596 5653 +57
Branches 1120 1147 +27
===========================================
+ Hits 4366 4422 +56
Misses 716 716
- Partials 514 515 +1
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
If a required property is a nullable reference type, will this allow the value to be resolved to null? |
(If it does not currently, I believe that it should) |
I think (am away from keyboard) that if a parameter is explicitly provided to set a property to null, it will accept it and initialise that property to null. But otherwise, Autofac doesn't have the concept of activating "null". It also feels weird to have the idea of "optional required" in the sense that having a nullable required property means the resolution is optional? It's either required or it isn't, surely? |
Hi @alistairjevans - The decision the LDMs made was that It was hotly debated at first, but eventually many folks came to appreciate that a required NRT could essentially represent a "suggested" member or a member that, if null, should be clearly stated as such. The following is valid code:
And it can be used like this:
but not like this:
I could be mistaken, but I am pretty certain that when AutoFac does constructor injection, it will provide null if a constructor argument is an unresolvable NRT. This is similar to what should happen if a NRT required property cannot be resolved. |
We will only provide allow a null value in the constructor (regardless of NRT), if an explicit default value is provided, i.e. |
Thinking on this some more, I'm not opposed to NRTs being considered optional dependencies, but the same rules would need to be applied to both constructor parameters and property types to be consistent I think. |
I think changing Autofac to allow "I explicitly initialized this to null" would break some things in insidious ways. I can't point to code right now (I'm on my phone) but I know we've always considered it a problem if a component registration returns null (eg, delegate activation returns null, provided parameter returns null). |
We cannot/will not initialise something to null; the real question here is what a nullable reference type can mean. I think we can safely separate the issue of required properties from that of nullable reference types. Allowing I'd suggest we raise a separate issue for discussion of "should Autofac take a nullable service to mean an optional one?", and discuss over there/seek opinions. |
I'll start the discussion issue because I've been thinking a lot about this. I need to run a couple of tests, but my gut says no: Autofac has more restrictions than the compiler because it isn't the compiler, it's an IoC container, and if a property is optional then it shouldn't be marked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of questions for clarification on some stuff. I don't think there's really anything to change, it looks really good. I am a little concerned over the new allocations since folks are very concerned about allocations these days. It's probably pretty trivial, all things considered.
RE: allocations, there should not be any additional allocations for users that aren't using property injection or required members. For people using property injection, we allocate a bit more upfront by capturing the injectable properties at the beginning and holding them in the activator, but we avoid re-allocating/fetching the |
…e extraneous local variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🍢
This change implements support for resolving
required
properties "by default", i.e. without requiringPropertiesAutowired
to be called.Here's an example from the tests:
Any required property must be resolvable, otherwise you get a
DependencyResolutionException
; for example if you miss out the registration forServiceB
in the above example:The exception message details all required properties that could not be set.
SetsRequiredMembers attribute
If the selected constructor used for activation based on available parameters and registrations has the
SetsRequiredMembers
attribute applied to it, we will make no attempt to set those required properties; we assume the constructor does it (making much the same assumption as the C# compiler).Parameters
Configured property parameters provided at registration time override any automatic population of required properties.
Other non-property parameters are also considered; (almost) any available parameter can provide a property value.
The almost above refers to the existing
NamedParameter
andPositionalParameter
, which cannot provide required property values. The reason for this is to do with clashing with constructors.NamedParameter
could have a legitimate constructor argumentvalue
, which is also the name of the parameter in every property setter. Likewise withPositionalParameter
, every property's parameter index is 0. These parameter types are ignored when checking required properties.Mixing Constructors and Properties
Constructors and required properties can be happily mixed together:
Only
ServiceB
above will be automatically injected as a required property.However, we do not check before setting a required property that it hasn't already been set, or is not null. So if a constructor populates a required property from a provided service, it will double-resolve. This is extremely hard to avoid in my mind, because it's nigh-on impossible to know whether the value already assigned to a property is some form of default, or a value set by a constructor. So, for safety, we always set required properties.
Inheritance Hierarchy
We look for the
RequiredMembersAttribute
all the way up the inheritance hierarchy, and all required properties in that hierarchy will be set.That is, unless the derived class' constructor has
SetsRequiredMembers
applied, in which case we don't set any properties in any of the parent types.Performance Impact
Required properties are slower than regular constructors, but not by a huge amount. Given these two components:
Here is the benchmark added (
RequiredPropertyBenchmark
):It actually uses less memory for required properties (slightly), because of the short-circuited simple constructor activation.
Breaking Changes
While technically there are no breaking signature changes, any user code that had required properties before will start to throw exceptions with this version if those properties aren't resolvable.
For that reason, I'd suggest this is a major incrementing change.
Closes #1355