Adding support for constrained open generics#635
Adding support for constrained open generics#635jbogard wants to merge 11 commits intoaspnet:masterfrom
Conversation
… close because of a generic constraint. See aspnet#471 for discussion.
|
|
||
| return genericType == parameter; | ||
| } | ||
| catch (Exception) |
There was a problem hiding this comment.
nitpick - this is supposed to catch line#295 right? Specifically for catch-alls, it would be clearer to make the try{} body as specific as possible, so perhaps catch in 296?
|
@muratg / @davidfowl - can you take a look? |
|
For posterity of "how do other containers handle this?": SimpleInjector is the odd one out here but @dotnetjunkie would know more I suppose about the approach he took. Unity is the only ASP.NET DI-supporting container I could find that does not support this feature. I'm not sure the impact, that package has 2k NuGet downloads and Autofac has >1M. |
| var specialConstraints = argumentDefinitionTypeInfo.GenericParameterAttributes; | ||
|
|
||
| if ((specialConstraints & GenericParameterAttributes.DefaultConstructorConstraint) | ||
| != GenericParameterAttributes.None) |
There was a problem hiding this comment.
Is this the preferred pattern vs an equality check?
if ((specialConstraints & GenericParameterAttributes.DefaultConstructorConstraint)
== GenericParameterAttributes.DefaultConstructorConstraint)
There was a problem hiding this comment.
Because it's a flag enum, I guess it's either that or Enum.HasFlag (which is really slow.
There was a problem hiding this comment.
Based on https://github.com/dotnet/corefx/blob/103639b6ff5aa6ab6097f70732530e411817f09b/src/Common/src/CoreLib/System/Reflection/GenericParameterAttributes.cs it is a simple bitmask, either code should work.
There was a problem hiding this comment.
Going to add some tests for these cases, is that better in the DI.Tests project?
|
|
||
| private static bool IsGenericTypeDefinedBy(Type type, Type openGeneric) | ||
| { | ||
| return !type.GetTypeInfo().ContainsGenericParameters |
There was a problem hiding this comment.
DRYer
{
var typeInfo = type.GetTypeInfo();
return !typeInfo.ContainsGenericParameters
&& typeInfo.IsGenericType
&& type.GetGenericTypeDefinition() == openGeneric;
}
| { | ||
| genericType = typeDefinition.MakeGenericType(genericArguments); | ||
| } | ||
| catch (Exception) |
There was a problem hiding this comment.
Should we catch only the expected exceptions? From the documentation, that's
InvalidOperationException | The current type does not represent a generic type definition. That is, IsGenericTypeDefinition returns false.
So we could/should be testing typeDefinition.IsGenericTypeDefinition first.
ArgumentNullException | typeArguments is null.-or-Any element of typeArguments is null.
This is demonstrably not true as we've already dereferenced that at line 287.
ArgumentException | The number of elements in typeArguments is not the same as the number of type parameters in the current generic type definition.-or-Any element of typeArguments does not satisfy the constraints specified for the corresponding type parameter of the current generic type.-or-typeArguments contains an element that is a pointer type (Type.IsPointer returns true), a by-ref type (Type.IsByRef returns true), or Void.
This one we have checked the length matches already, but the rest of the criteria could have been violated. Thus we do need to catch (ArgumentException)
NotSupportedException | The invoked method is not supported in the base class. Derived classes must provide an implementation.
This one might be thrown, not sure we can prevent that. Either someone can request a deeper dive or we can make a case that we do need to catch (NotSupportedException) too.
|
What about this case? |
IDisposable
left a comment
There was a problem hiding this comment.
Looks good now.
One final thought. Do we want to do any introspection or white-listing of the DI containers to know which ones will support this constraining and either throw or just return true; from the IsCompatibleWithGenericParameterConstraints method when the DI doesn't work with these constraints?
|
Would existing containers even be affected? If they didn't support it before, they blew up with exceptions. Unity for example just blows up with that From my understanding of the extensions, they either replace |
|
Perfect then... just wasn't wrapping my head around the full use case. |
It doesn't exactly work like that. As soon as a feature is added to DI.Specification AspNetCore might take a dependency on it and would start exploding with containers that used to run fine on. @jbogard did you validate that all mentioned containers actually pass new spec test? @ENikS what are your thought on this feature? Do you think it's worth adding? Would you be willing/able to support it in unity? |
|
How does this change affect time to first service benchmark (https://github.com/aspnet/DependencyInjection/blob/dev/benchmarks/DI.Performance/TimeToFirstServiceBenchmark.cs)? |
|
What's the implication of moving it out of the DI.Specification? I just want the feature in this container, I'm less concerned if every container does. Though I wasn't sure if features are added to this container just for ASP.NET Core, or other libraries that also want to use it. I'll update with benchmark #'s. I've also opened an issue with Unity here: unitycontainer/container#81 I can also open a PR with them. |
|
Benchmarks after: https://gist.github.com/jbogard/20f026885a79fb5f0227f577d16397f0 Benchmarks on dev: https://gist.github.com/jbogard/82db983fe5d1518bc8bd230211422db7 |
|
I would like to implement this feature but I am not sure what is correct behavior. Is there a concise description of how it should behave? Correct me if I am wrong. I am assuming it applies to either resolving collection or single service. When resolving collection it should return only these that satisfy constraint and when resolving single service it should blow? |
|
@ENikS it mainly concerns collection behavior. If I'm resolving It really isn't for single services, but most containers I've checked just let it apply to both, and let the parent service's needs dictate the behavior (if I require a service, and none exists, blow up). The real-world scenarios are in cases where I still need the generic parameter for a further dependency. I want the generic parameter to "flow" down, instead of terminating with a base type. Keeping In terms of what it means to support this behavior, all containers just put a try-catch around closing an open generic type (some try to catch the simple cases without just catch-first) since the methods to actually test a closed type aren't public in the BCL. See the links above for how other containers do it. It can be pretty simple to support, just as simple as try-catch-don't match. |
|
I will add this to next patch release of Unity. |
|
Correction regarding DryIoc. It does the filtering when registering by walking down the constraints. The |
|
Autofac does the same, but there are wonky edge-cases that basically only
the compiler catches right now. Tries to do its best and catches as the
last resort. Other containers weren't so brave as to try to mimic the
compiler ;)
…On Thu, Apr 12, 2018 at 8:37 AM, Maksim Volkau ***@***.***> wrote:
Correction regarding DryIoc.
It does the filtering when registering by walking down the constraints
<https://bitbucket.org/dadhi/dryioc/src/12245e67f615873a89482eed7cfcf5dd751eaac2/DryIoc/Container.cs?at=dev&fileviewer=file-view-default#Container.cs-9357>
.
The try-catch in the link above
<https://bitbucket.org/dadhi/dryioc/src/e788ffce78b727b35fda5355a2297d40f0e7731b/DryIoc/Container.cs?at=default&fileviewer=file-view-default#Container.cs-7465>
is for resolution phase to wrap the unlikely failed match in container
exception.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#635 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAGYMrNxb-gjgduSSHA_wJBuK9C42pH1ks5tn1i2gaJpZM4TNU2C>
.
|
|
As of v5.8.6 Unity supports constrained generics |
|
@jbogard Wanted to make sure to ask this. If you are adding support for generics, my assumption is that the following example would work out using the And I can just register my type as below: I apologize as I haven't read the entire conversation. |
|
Not sure who you asking but in Unity it is supported scenario. |
|
@ENikS I was actually asking @jbogard but that doesn't matter. I have used Unity in all of my ASP.NET apps. So I am familiar with it. I was trying to go with the new built-in container included in ASP.NET Core for any new work. But if sticking to Unity is the only viable solution, then I will stick with it using the Unity.Microsoft.DependencyInjection package. |
|
Resolving |
|
@muratg lets put this into the 2.2 milestone for consideration. |
|
@muratg So we're in the 2.2 window now...thoughts? |
|
bump |
|
@davidfowl @pakrym please take a look. |
|
@davidfowl could you either merge or close this? @natemcmaster is going to move this to the mondo repo and moving PRs are tricky. Thanks. |
|
I vote the first ;)
…On Wed, Oct 17, 2018 at 5:10 PM Murat Girgin ***@***.***> wrote:
@davidfowl <https://github.com/davidfowl> could you either merge or close
this? @natemcmaster <https://github.com/natemcmaster> is going to move
this to the mondo repo and moving PRs are tricky. Thanks.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#635 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAGYMmYawtJ75SkkLOCwE5HqlfhY_6lSks5ul6rJgaJpZM4TNU2C>
.
|
|
We would like to get this feature in for 3.0 but in a bit different shape (I managed to convince @davidfowl to catch first chance exceptions). It means, unfortunately, this PR would have to be closed and reopened against new repo. |
|
I’ll reopen on the new repo when it shows up then.
…On Wed, Oct 17, 2018 at 5:44 PM Pavel Krymets ***@***.***> wrote:
We would like to get this feature in for 3.0 but in a bit different shape
(I managed to convince @davidfowl <https://github.com/davidfowl> to catch
first chance exceptions). It means, unfortunately, this PR would have to be
closed and reopened against new repo.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#635 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAGYMkd2XRmpyv0YNhE4UyvaP6W9UHG3ks5ul7K1gaJpZM4TNU2C>
.
|
|
Thanks @jbogard! |
|
first chance exceptions are the 👿. We should do what we can in ns2.0 and ask for new APIs in corefx to make it possible to avoid throwing. |
|
@davidfowl I agree but this is what every container developer has had to deal with. May be easier now to expose the APIs now. |
(again)
See #471 for details. Figured I'd give this another shot.
The general scenario is:
Suppose I have some interface,
IRepository<T>. This interface allows me to save an entity to a database, perhaps wrapping EFCoreDbContext. As part of saving, I want to validate so I haveIValidator<T>.The implementation
DbContextRepository<T>uses an enumerable ofIValidator<T>to validate:I want to be able to support:
instead of
For the reasons in #471.
In case anything has changed, just wanted to throw this out there. I modified the approach to do what Autofac does, which is actually check the constraints if they exist (instead of the previous try-catch-swallow approach).