The ClaimsPrincipal.SelectPrimaryIdentity method (which is used by the public Identity property) always allocates an enumerator, when this should never be necessary.
The method is declared as taking an IEnumerable<ClaimsIdentity>. In normal usage, this argument is always the List<ClaimsIdentity> from the _identities field. (There's an obscure situation in which it might not be: user code might retrieve the static PrimaryIdentitySelector property and invoke it passing in their own IEnumerable<ClaimsIdentity>.)
This means that the foreach inside SelectPrimaryIdentity can't take advantage of the zero-allocation enumeration normally possible with a List<T>, because the CLR has to generate code that can cope with any IEnumerable<T>. So the overwhelming majority of use cases pay an allocation penalty that is unnecessary.
This could be modified to detect an IList as a special case to avoid the allocation.
Configuration
.NET 8.0.8, SDK 8.0.400. All architectures, any OS.
Regression?
No.
Analysis
We saw this allocation in a performance analysis. We were able to prevent it by writing this helper:
public static IIdentity? GetPrimaryIdentity(this ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal.Identities is IList<ClaimsIdentity> identities)
{
for (int i = 0; i < identities.Count; i++)
{
if (identities[i] is not null)
{
return identities[i];
}
}
}
return claimsPrincipal.Identity;
}
and using it instead of user.Identity. Running our performance analysis again with this in place showed that we no longer saw an enumerator being allocated.
Description
The
ClaimsPrincipal.SelectPrimaryIdentity
method (which is used by the publicIdentity
property) always allocates an enumerator, when this should never be necessary.The method is declared as taking an
IEnumerable<ClaimsIdentity>
. In normal usage, this argument is always theList<ClaimsIdentity>
from the_identities
field. (There's an obscure situation in which it might not be: user code might retrieve the staticPrimaryIdentitySelector
property and invoke it passing in their ownIEnumerable<ClaimsIdentity>
.)This means that the
foreach
insideSelectPrimaryIdentity
can't take advantage of the zero-allocation enumeration normally possible with aList<T>
, because the CLR has to generate code that can cope with anyIEnumerable<T>
. So the overwhelming majority of use cases pay an allocation penalty that is unnecessary.This could be modified to detect an
IList
as a special case to avoid the allocation.Configuration
.NET 8.0.8, SDK 8.0.400. All architectures, any OS.
Regression?
No.
Analysis
We saw this allocation in a performance analysis. We were able to prevent it by writing this helper:
and using it instead of
user.Identity
. Running our performance analysis again with this in place showed that we no longer saw an enumerator being allocated.