Skip to content

[DependencyInjection] Use '==' instead of 'Equals()` to test service type equality - #90334

Merged
benjaminpetit merged 1 commit into
dotnet:mainfrom
benjaminpetit:fix/scrutor
Aug 14, 2023
Merged

[DependencyInjection] Use '==' instead of 'Equals()` to test service type equality#90334
benjaminpetit merged 1 commit into
dotnet:mainfrom
benjaminpetit:fix/scrutor

Conversation

@benjaminpetit

Copy link
Copy Markdown
Contributor

Related issue: dotnet/aspnetcore#49485

It seems that scrutor uses a custom implementation of Type: https://github.com/khellang/Scrutor/blob/master/src/Scrutor/DecoratedType.cs#L15

Unfortunately, this type override the Equals() method, making it non "symetrical" with Type:

Type t = typeof(object);
var decoratedType = new DecoratedType(t);
var result1 = t.Equals(decoratedType); // returns true
var result2 = decoratedType.Equals(t); // returns false

Using == to test if two types are the same seems to fix the issue.

@ghost

ghost commented Aug 10, 2023

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

Related issue: dotnet/aspnetcore#49485

It seems that scrutor uses a custom implementation of Type: https://github.com/khellang/Scrutor/blob/master/src/Scrutor/DecoratedType.cs#L15

Unfortunately, this type override the Equals() method, making it non "symetrical" with Type:

Type t = typeof(object);
var decoratedType = new DecoratedType(t);
var result1 = t.Equals(decoratedType); // returns true
var result2 = decoratedType.Equals(t); // returns false

Using == to test if two types are the same seems to fix the issue.

Author: benjaminpetit
Assignees: benjaminpetit
Labels:

area-Extensions-DependencyInjection

Milestone: -

@IDisposable

Copy link
Copy Markdown
Contributor

Seems like a design flaw on the Scrutor side, honestly

if (ServiceKey == null && other.ServiceKey == null)
{
return ServiceType.Equals(other.ServiceType);
return ServiceType == other.ServiceType;

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.

operator== is overloaded for System.Type and it just forwards the call to Equals unless the type is RuntimeType: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Type.cs#L654-L666

Is it actually fixing anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As long as you are trying to compare RuntimeType against DecoratedType at least.

The comment

// If `left` is a non-runtime type with a weird Equals implementation
// this is where operator `==` would differ from `Equals` call.

seems to indicate that someone else already experienced that...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we add a test for this?

@khellang

Copy link
Copy Markdown
Member

Seems like a design flaw on the Scrutor side, honestly

@IDisposable How come?

@khellang

khellang commented Aug 11, 2023

Copy link
Copy Markdown
Member

Maybe the fix is to compare UnderlyingSystemType instead? I think this might be Best Practice™ anyway? See When is a Type not a Type?

@khellang

Copy link
Copy Markdown
Member

Unfortunately, this type override the Equals() method, making it non "symetrical" with Type

That's the entire reason for the derived class. Scrutor used to do decoration by replacing existing ServiceDescriptor instances with a factory-based ServiceDescriptor that uses ActivatorUtilities to create an instance of the decorator, closing over an instance of the decoratee. The problem with this approach is that the registration of the decoratee is removed from the service collection and subsequently instance lifetime is no longer tracked for disposal by the service provider. This means that decorated instances weren't being disposed.

This was solved by DecoratedType having reference-based equality, effectively allowing you to have multiple instances of the same type in the service collection. When the decorator is resolved, it just "forwards" to the decoratee's registration, allowing the container to track the decoratee instance for disposal.

@benjaminpetit

Copy link
Copy Markdown
Contributor Author

Maybe the fix is to compare UnderlyingSystemType instead? I think this might be Best Practice™ anyway? See When is a Type not a Type?

That's what Type.Equals() do. And that's causing a lot of confusion during dictionary lookup/insertion, because it will compare it to the DecoratedType.UnderlyingSystemType that will return the proxied UnderlyingSystemType...

Type t = typeof(object);
var decoratedType = new DecoratedType(t);
var result1 = t.Equals(decoratedType); // returns false
var result2 = decoratedType.Equals(t); // returns false

Is what you want.

@IDisposable

IDisposable commented Aug 11, 2023

Copy link
Copy Markdown
Contributor

Seems like a design flaw on the Scrutor side, honestly

@IDisposable How come?

Seems like the wrapper class that Scrutor is generating having only reference-equality is what's wrong here... the class is proxying/wrapping for some other class representing type, if it can't enforce the identity (e.g. always handing out the same instance for the underlying/wrapped type) then it shouldn't be enforcing reference identity.

@steveharter steveharter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A test would be nice, but not blocking.

@benjaminpetit
benjaminpetit merged commit 1fa3eac into dotnet:main Aug 14, 2023
Carl-Hugo added a commit to Carl-Hugo/An-Atypical-ASP.NET-Core-8-Design-Patterns-Guide that referenced this pull request Aug 20, 2023
The CI Build failure is related to the following:

- khellang/Scrutor#208
- dotnet/runtime#90334
@ghost ghost locked as resolved and limited conversation to collaborators Sep 14, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants