Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Type activator should be order agnostic - #67493

Closed
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic
Closed

Type activator should be order agnostic#67493
mapogolions wants to merge 30 commits into
dotnet:mainfrom
mapogolions:type-activator-should-be-order-agnostic

Conversation

@mapogolions

Copy link
Copy Markdown
Contributor

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

@ghostghost added area-Extensions-DependencyInjection community-contribution Indicates that the PR has been added by a community member labels Apr 2, 2022
@ghost

ghost commented Apr 2, 2022

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

If we have a type with some number of constructors, then the activator always finds the best constructor to activate the instance. This is independent of the order in which these constructors were defined.
Let's look at the following class

classValidationResult{publicValidationResult(ValidationStatusstatus,stringdescription,IReadOnlyDictionary<string,object>data){Status=status;Description=description;Data=data;}publicValidationResult(stringdescription,IReadOnlyDictionary<string,object>data):this(ValidationStatus.Valid,description,data){}publicValidationStatusStatus{get;}publicstringDescription{get;}publicIReadOnlyDictionary<string,object>Data{get;}}
varinstance=ActivatorUtilities.CreateInstance<ValidationResult>(serviceProvider,"description",data);

The activator will select the second constructor. (i.g. the statement instance.Status is ValidationStatus.Valid will be true)

But if we start using the ActivatorUtilitiesConstructor attribute, then the order in which constructors are defined starts to affect the final result.
Please see unit tests for more details

Author:mapogolions
Assignees:-
Labels:

area-Extensions-DependencyInjection

Milestone:-

if (isPreferred)
if (preferredCtors.Length == 1)
{
bestMatcher = new ConstructorMatcher(preferredCtors[0]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is wasteful to construct an array, then only use its first element and throw away the rest. This block can be structured like this:

ConstructorInfo?constructorInfo=null;foreach(ConstructorInfo?ctorinctors){if(ctor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute))){if(constructorInfois not null){ThrowMultipleCtorsMarkedWithAttributeException();}constructorInfo=ctor;}}if(constructorInfois not null){ConstructorMatcherbestMatcher=new(constructorInfo);bestLength=bestMatcher.Match(parameters);if(bestLength==-1){ThrowMarkedCtorDoesNotTakeAllProvidedArguments();}}

This will also make using System.Linq; unnecessary.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 Thanks for review. Good point

{
bestLength = length;
bestMatcher = matcher;
var matcher = new ConstructorMatcher(constructor);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
varmatcher=newConstructorMatcher(constructor);
if(constructorisnull)
{
continue;
}
varmatcher=newConstructorMatcher(constructor);

@mapogolionsmapogolionsApr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 What do you think if we just replace ConstructorInfo? with the ConstructorInfo in the foreach-loop foreach (ConstructorInfo?ConstructorInfo constructor in constructors)? According to documentation the GetConstructors() call should not return collection with nullable elements. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructors?view=net-6.0#system-type-getconstructors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. Based on the usage elsewhere in the repo, null is not checked. GetConstructor (singular), however, returns nullable object,

@eerhardt

Copy link
Copy Markdown
Member

Thanks @mapogolions. Can you also ensure the scenarios listed in #46132 are addressed here as well?

Likewise, while we are fixing this bug, we should ensure the .NET Maui scenario is fixed as well, where the order of constructors looks like this:

publicLoginView(RandomItem item){//this is constructor is invoked by code and RandomItem is not registered in DI//therefore it should be skipped by ActivatorUtilities.CreateInstanceInitializeComponent();}// since there are no other valid constructors, ActivatorUtilities.CreateInstance should pick the default ctorpublicLoginView(){InitializeComponent();}

@mapogolions

mapogolions commented Apr 3, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Initially this PIR fixed this problem #42339
To fix this one #46132 need to a little bit tweak the algorithm for picking up the best constructor. In the last commits, I tried to do it. Please review. I don't know how the idea of fallback constructors will affect performance.

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this fix up @mapogolions. It looks like a great start.

FYI - @davidfowl@halter73@Tratcher - in case you want to take a look and have any feedback.

{
public class ActivatorUtilitiesTests
{
[Fact]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this test. Can you also add one that ensures the default constructor is picked in this scenario?

dotnet/maui#4318 (comment)

Basically:

[Theory][InlineData(typeof(DefaultConstructorFirst)][InlineData(typeof(DefaultConstructorLast)]
public voidChoosesDefaultConstructorNoMatterOrder(TypeinstanceType){var services =newServiceCollection();usingvarprovider= services.BuildServiceProvider();var instance =ActivatorUtilities.CreateInstance(provider,instanceType);Assert.NotNull(instance);}public class DefaultConstructorFirst
{publicA A {get;}
public B B {get;}
public DefaultConstructorFirst(){}
public DefaultConstructorFirst(ClassAa){A=a;}
public DefaultConstructorFirst(ClassAa,ClassBb){A=a;B=b;}}
public classDefaultConstructorLast{
public A A {get;}
public B B {get;}
public DefaultConstructorLast(ClassAa,ClassBb){A=a;B=b;}
public DefaultConstructorLast(ClassAa){A=a;}
public DefaultConstructorLast(){}}

_parameterValues = new object?[_parameters.Length];
}

public int ApplyExectLength { get; private set; } = -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintApplyExactLength{get;privateset;}=-1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe picking a better name here would help. How about

Suggested change
publicintApplyExectLength{get;privateset;}=-1;
publicintMatchedLength{get;privateset;}=-1;

foreach (ConstructorInfo constructor in constructors)
{
foreach (ConstructorInfo? constructor in instanceType.GetConstructors())
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code passes false in for inherit. The new code uses a different overload that passes true for inherit. It's possible that this doesn't really matter for constructors, but I'd prefer to limit the changes to only what is necessary to fix the issue.

Suggested change
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute)))
if(constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute),false))

}

return bestMatcher.CreateInstance(provider);
var matchers = new ConstructorMatcher[constructors.Length];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about 2 optimizations here:

  1. When there is only a single constructor, just use it.
  2. When there are more than one, is there a way to not allocate an array here? Possibly we could stackalloc up to a reasonable count? Maybe 5 or 10? If the Type has more than that, then allocating an array seems OK since it won't be that common.

Maybe if we do (2), then special-casing (1) becomes unnecessary.

{
ConstructorInfo constructor = constructors[i];
var matcher = new ConstructorMatcher(constructor);
_ = matcher.Match(parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well make the Match method return void since no one is consuming the return value anymore.

@Tratcher

Copy link
Copy Markdown
Member

Prior related discussion: dotnet/aspnetcore#2871

@mapogolions

mapogolions commented Apr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

@eerhardt
Thanks for feedback. Could you please review changes. As I understand from the comment above the ActivatorUtilities class should use the longest available constructor now. I've tried to address it. The new requirement breaks the following test case so I fixed it.


var instance = ActivatorUtilities.CreateInstance<Creatable>(provider, a, c);

Assert.Null(instance.B);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should B be null here? There is a scoped service for B, so shouldn't the ctor that takes a B be picked?

@mapogolionsmapogolionsApr 29, 2022

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand from dotnet/aspnetcore#2915, the longest available constructor should only be used if competing constructors
have the same priority/score (the value of the MatchedLength property reflects it)

Creatable class has two constructors

  • Ctor(A a, B b, C c, S s)
  • Ctor(A a, C c, S s) : this(a, null, c, s)

Let's look at the following 3 examples

  1. ActivatorUtilities.CreateInstace(provider, new A(), new C());

According to the algorithm that was invented and used now and which I took as a basis, the first ctor is given score 1, the second one is given score 2 (2 given arguments match sequentially). As result the second constructor will be picked up (b is null)

  1. ActivatorUtilities.CreateInstance(provider, new A())

The first ctor is given score 1, the second ctor is given score 1. We fall into a situation where we have competing constructors. In this case, the rule about the longest available constructor comes into play.

  1. ActivatorUtilities.CreateInstance(provider, new C(), new A()) or ActivatorUtilities.CreateInstance(provider)
    Same as above, except for the fact that competing constructors are given score 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion in #46132, it is asking for an ambiguous exception to be thrown to be thrown in this case. Which seems like the right thing IMO. If there are multiple ctors that we can't really pick between, it is better to throw and say "use the ActivatorUtilitiesConstructorAttribute to disambiguate". It is really hard to define perfect behavior here when the "given arguments" and the "services available" can intermix.

See also all the discussion on #46132 for all the scenarios, and the intended behaviors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mapogolions - any thoughts on this?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I don't see a way to satisfy all the mentioned requirements (especially ambiguity detection). Feel free to close this as a dead end.

@eerhardteerhardtJun 1, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using the new (in 6.0) IServiceProviderIsService interface to test if a Type is available as a service in the IServiceProvider?

/// <summary>
/// Optional service used to determine if the specified type is available from the <see cref="IServiceProvider"/>.
/// </summary>
publicinterfaceIServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
boolIsService(TypeserviceType);
}

If the IServiceProvider doesn't support this new interface, then using the algorithm proposed here?

@eerhardt

Copy link
Copy Markdown
Member

Closing as per #67493 (comment). Will either re-open or open a new PR to fix this issue.

@ghostghost locked as resolved and limited conversation to collaborators Jul 27, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Extensions-DependencyInjectioncommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mapogolions@eerhardt@Tratcher@am11@maryamariyan