Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/contributing/Compiler Test Plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ This document provides guidance for thinking about language interactions and tes
- Readonly members on structs (methods, property/indexer accessors, custom event accessors)
- SkipLocalsInit
- Method override or explicit implementation with `where T : { class, struct, default }`
- `extension` blocks

# Code
- Operators (see Eric's list below)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2121,7 +2121,7 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind
{
Error(diagnostics, ErrorCode.ERR_InvalidPrimaryConstructorParameterReference, node, parameter);
}
else if (parameter.ContainingSymbol is NamedTypeSymbol { IsExtension: true } &&
else if (parameter.IsExtensionParameter() &&
(InParameterDefaultValue || InAttributeArgument ||
this.ContainingMember() is not { Kind: not SymbolKind.NamedType, IsStatic: false } || // We are not in an instance member
(object)this.ContainingMember().ContainingSymbol != parameter.ContainingSymbol) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ internal void ValidateParameterNameConflicts(
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionTypeParameter, GetLocation(p), name);
}
}
else if (p.ContainingSymbol is NamedTypeSymbol { IsExtension: true })
else if (p.IsExtensionParameter())
{
diagnostics.Add(ErrorCode.ERR_TypeParameterSameNameAsExtensionParameter, tp.GetFirstLocationOrNone(), name);
}
Expand Down
16 changes: 13 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/ExecutableCodeBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,24 @@ public static void ValidateIteratorMethod(CSharpCompilation compilation, MethodS
return;
}

foreach (var parameter in iterator.Parameters)
var parameters = !iterator.IsStatic
? iterator.GetParametersIncludingExtensionParameter()
Copy link
Member

@jjonescz jjonescz Apr 3, 2025

Choose a reason for hiding this comment

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

Should the GetParametersIncludingExtensionParameter helper check that the member is not static? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

That would not be convenient. That method is also used in overload resolution, and for the purpose of overload resolution, we do want to account for the extension parameter even when the method is static (the static receiver counts as an argument in that case).

: iterator.Parameters;

foreach (var parameter in parameters)
{
bool isReceiverParameter = parameter.IsExtensionParameter();
if (parameter.RefKind != RefKind.None)
{
diagnostics.Add(ErrorCode.ERR_BadIteratorArgType, parameter.GetFirstLocation());
var location = isReceiverParameter
? ((MethodDeclarationSyntax)iterator.GetNonNullSyntaxNode()).Identifier.GetLocation()
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2025

Choose a reason for hiding this comment

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

((MethodDeclarationSyntax)iterator.GetNonNullSyntaxNode()).Identifier.GetLocation()

Wouldn't simply requesting location from iterator work? #Closed

: parameter.GetFirstLocation();

diagnostics.Add(ErrorCode.ERR_BadIteratorArgType, location);
}
else if (parameter.Type.IsPointerOrFunctionPointer())
else if (parameter.Type.IsPointerOrFunctionPointer() && !isReceiverParameter)
{
// We already reported an error elsewhere if the receiver parameter of an extension is a pointer type.
diagnostics.Add(ErrorCode.ERR_UnsafeIteratorArgType, parameter.GetFirstLocation());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,9 @@ internal static bool HasInternalAccessTo(this AssemblySymbol fromAssembly, Assem

internal static ErrorCode GetProtectedMemberInSealedTypeError(NamedTypeSymbol containingType)
{
return containingType.TypeKind == TypeKind.Struct ? ErrorCode.ERR_ProtectedInStruct : ErrorCode.WRN_ProtectedInSealed;
return containingType.IsExtension ? ErrorCode.ERR_ProtectedInExtension
: containingType.TypeKind == TypeKind.Struct ? ErrorCode.ERR_ProtectedInStruct
: ErrorCode.WRN_ProtectedInSealed;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3879,7 +3879,7 @@ private static EffectiveParameters GetEffectiveParametersInNormalForm<TMember>(
hasAnyRefOmittedArgument = false;

bool isNewExtensionMember = member.GetIsNewExtensionMember();
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2025

Choose a reason for hiding this comment

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

bool isNewExtensionMember = member.GetIsNewExtensionMember();

Consider moving declaration closer to usage #Closed

ImmutableArray<ParameterSymbol> parameters = isNewExtensionMember ? GetParametersIncludingReceiver(member) : member.GetParameters();
ImmutableArray<ParameterSymbol> parameters = member.GetParametersIncludingExtensionParameter();

// We simulate an extra parameter for vararg methods
int parameterCount = parameters.Length + (member.GetIsVararg() ? 1 : 0);
Expand Down Expand Up @@ -4040,7 +4040,7 @@ private static EffectiveParameters GetEffectiveParametersInExpandedForm<TMember>
var types = ArrayBuilder<TypeWithAnnotations>.GetInstance();
var refs = ArrayBuilder<RefKind>.GetInstance();
bool anyRef = false;
var parameters = member.GetIsNewExtensionMember() ? GetParametersIncludingReceiver(member) : member.GetParameters();
var parameters = member.GetParametersIncludingExtensionParameter();
bool hasAnyRefArg = argumentRefKinds.Any();
hasAnyRefOmittedArgument = false;
TypeWithAnnotations paramsIterationType = default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ private static void ReportMissingRequiredParameter(
// to required formal parameter 'y'.

TMember badMember = bad.Member;
ImmutableArray<ParameterSymbol> parameters = badMember.GetIsNewExtensionMember() ? OverloadResolution.GetParametersIncludingReceiver(badMember) : badMember.GetParameters();
ImmutableArray<ParameterSymbol> parameters = badMember.GetParametersIncludingExtensionParameter();
int badParamIndex = bad.Result.BadParameter;
string badParamName;
if (badParamIndex == parameters.Length)
Expand Down Expand Up @@ -1115,7 +1115,7 @@ private bool HadBadArguments(
// as there is no explicit call to Add method.

int argumentOffset = arguments.IncludesReceiverAsArgument ? 1 : 0;
var parameters = method.GetIsNewExtensionMember() ? OverloadResolution.GetParametersIncludingReceiver(method) : method.GetParameters();
var parameters = method.GetParametersIncludingExtensionParameter();

for (int i = argumentOffset; i < parameters.Length; i++)
{
Expand Down Expand Up @@ -1170,7 +1170,7 @@ private static void ReportBadArgumentError(

// Early out: if the bad argument is an __arglist parameter then simply report that:

var parameters = method.GetIsNewExtensionMember() ? OverloadResolution.GetParametersIncludingReceiver(method) : method.GetParameters();
var parameters = method.GetParametersIncludingExtensionParameter();
if (method.GetIsVararg() && parm == parameters.Length)
{
// NOTE: No SymbolDistinguisher required, since one of the arguments is "__arglist".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@ public ImmutableArray<int> ToImmutableArray()
}
}

internal static ImmutableArray<ParameterSymbol> GetParametersIncludingReceiver(Symbol symbol)
{
Debug.Assert(symbol.GetIsNewExtensionMember());
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider optimizing
return [symbol.ContainingType.ExtensionParameter, .. symbol.GetParameters()];
}

private static ImmutableArray<TypeWithAnnotations> GetParameterTypesIncludingReceiver(Symbol symbol)
{
Debug.Assert(symbol.GetIsNewExtensionMember());
Expand All @@ -79,7 +72,7 @@ private static ArgumentAnalysisResult AnalyzeArguments(
Debug.Assert(arguments != null);

bool isNewExtensionMember = symbol.GetIsNewExtensionMember();
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2025

Choose a reason for hiding this comment

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

bool isNewExtensionMember = symbol.GetIsNewExtensionMember();

It looks like the local is no longer used #Closed

ImmutableArray<ParameterSymbol> parameters = isNewExtensionMember ? GetParametersIncludingReceiver(symbol) : symbol.GetParameters();
ImmutableArray<ParameterSymbol> parameters = symbol.GetParametersIncludingExtensionParameter();
bool isVararg = symbol.GetIsVararg();

// The easy out is that we have no named arguments and are in normal form.
Expand Down
23 changes: 22 additions & 1 deletion src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2525,7 +2525,7 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep
<value> The parameter modifier '{0}' cannot be used with '{1}'</value>
</data>
<data name="ERR_BadTypeforThis" xml:space="preserve">
<value>The first parameter of an extension method cannot be of type '{0}'</value>
<value>The receiver parameter of an extension cannot be of type '{0}'</value>
</data>
<data name="ERR_BadParamModThis" xml:space="preserve">
<value>A parameter array cannot be used with 'this' modifier on an extension method</value>
Expand Down Expand Up @@ -5914,6 +5914,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_InExtensionMustBeValueType" xml:space="preserve">
<value>The first 'in' or 'ref readonly' parameter of the extension method '{0}' must be a concrete (non-generic) value type.</value>
</data>
<data name="ERR_InExtensionParameterMustBeValueType" xml:space="preserve">
<value>The 'in' or 'ref readonly' receiver parameter of extension must be a concrete (non-generic) value type.</value>
</data>
<data name="ERR_FieldsInRoStruct" xml:space="preserve">
<value>Instance fields of readonly structs must be readonly.</value>
</data>
Expand All @@ -5932,6 +5935,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_RefExtensionMustBeValueTypeOrConstrainedToOne" xml:space="preserve">
<value>The first parameter of a 'ref' extension method '{0}' must be a value type or a generic type constrained to struct.</value>
</data>
<data name="ERR_RefExtensionParameterMustBeValueTypeOrConstrainedToOne" xml:space="preserve">
<value>The 'ref' receiver parameter of an extension block must be a value type or a generic type constrained to struct.</value>
</data>
<data name="ERR_OutAttrOnInParam" xml:space="preserve">
<value>An in parameter cannot have the Out attribute.</value>
</data>
Expand Down Expand Up @@ -8134,4 +8140,19 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_PPIgnoredFollowsIf" xml:space="preserve">
<value>'#:' directives cannot be after '#if' directive</value>
</data>
<data name="ERR_ProtectedInExtension" xml:space="preserve">
<value>'{0}': new protected member declared in an extension block</value>
</data>
<data name="ERR_InstanceMemberWithUnnamedExtensionsParameter" xml:space="preserve">
<value>'{0}': cannot declare instance members in an extension block with an unnamed receiver parameter</value>
</data>
<data name="ERR_InitInExtension" xml:space="preserve">
<value>'{0}': cannot declare init-only accessors in an extension block</value>
</data>
<data name="ERR_ModifierOnUnnamedReceiverParameter" xml:space="preserve">
<value>Cannot use modifiers on the unnamed receiver parameter of extension block</value>
</data>
<data name="ERR_ExtensionTypeNameDisallowed" xml:space="preserve">
<value>Types and aliases cannot be named 'extension'.</value>
</data>
</root>
8 changes: 8 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2390,6 +2390,14 @@ internal enum ErrorCode
ERR_PPIgnoredNeedsFileBasedProgram = 9298,
ERR_PPIgnoredFollowsIf = 9299,

ERR_RefExtensionParameterMustBeValueTypeOrConstrainedToOne = 9300,
ERR_InExtensionParameterMustBeValueType = 9301,
ERR_ProtectedInExtension = 9302,
ERR_InstanceMemberWithUnnamedExtensionsParameter = 9303,
ERR_InitInExtension = 9304,
ERR_ModifierOnUnnamedReceiverParameter = 9305,
ERR_ExtensionTypeNameDisallowed = 9306,

// Note: you will need to do the following after adding errors:
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)
// 2) Add message to CSharpResources.resx
Expand Down
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2506,6 +2506,13 @@ or ErrorCode.ERR_ExpressionTreeContainsExtensionPropertyAccess
or ErrorCode.ERR_PPIgnoredFollowsToken
or ErrorCode.ERR_PPIgnoredNeedsFileBasedProgram
or ErrorCode.ERR_PPIgnoredFollowsIf
or ErrorCode.ERR_RefExtensionParameterMustBeValueTypeOrConstrainedToOne
or ErrorCode.ERR_InExtensionParameterMustBeValueType
or ErrorCode.ERR_ProtectedInExtension
or ErrorCode.ERR_InstanceMemberWithUnnamedExtensionsParameter
or ErrorCode.ERR_InitInExtension
or ErrorCode.ERR_ModifierOnUnnamedReceiverParameter
or ErrorCode.ERR_ExtensionTypeNameDisallowed
=> false,
};
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ protected override void GenerateControlFields()
// Add a field: bool disposeMode
_disposeModeField = F.StateMachineField(boolType, GeneratedNames.MakeDisposeModeFieldName());

if (_isEnumerable && this.method.Parameters.Any(static p => p.IsSourceParameterWithEnumeratorCancellationAttribute()))
if (_isEnumerable && this.method.Parameters.Any(static p => !p.IsExtensionParameterImplementation() && p.HasEnumeratorCancellationAttribute))
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2025

Choose a reason for hiding this comment

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

!p.IsExtensionParameterImplementation()

It is not obvious why extension parameter is getting special treatment #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean we should leave a comment here (the attribute is ignored on extension parameter) or do you mean you think the attribute should be effective on the extension parameter too?

The reason I don't think the attribute should be effective is that this attribute is very specific to async-iterator method implementations. I don't think it belongs on an extension parameter, as the extension block may contain many kinds of members. Also, the scenario where you need an async-iterator extension method on a CancellationToken receiver seems very niche to say the least.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it belongs on an extension parameter

I am fine with this design decision, Could you please mention this explicitly in the speclet?

Also, please make sure to test scenario where extension parameter implementation is the only parameter for the method.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good. Linked to corresponding spec update.
The test ReceiverParameterValidation_CancellationTokenParameter_Instance shows that the attribute doesn't have an effect (it includes another parameter, but I think that's fine)

Copy link
Contributor

Choose a reason for hiding this comment

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

it includes another parameter, but I think that's fine

Are you saying the CancellationToken parameter doesn't have to be the last parameter in a general case?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's correct. The only restriction is that only one parameter may be marked with this attribute.

{
// Add a field: CancellationTokenSource combinedTokens
_combinedTokensField = F.StateMachineField(
Expand Down Expand Up @@ -211,7 +211,8 @@ protected override BoundStatement InitializeParameterField(MethodSymbol getEnume
{
BoundStatement result;
if (_combinedTokensField is object &&
parameter.IsSourceParameterWithEnumeratorCancellationAttribute() &&
!parameter.IsExtensionParameterImplementation() &&
parameter.HasEnumeratorCancellationAttribute &&
parameter.Type.Equals(F.Compilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken), TypeCompareKind.ConsiderEverything))
{
// For a parameter of type CancellationToken with [EnumeratorCancellation]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ protected override (TypeWithAnnotations ReturnType, ImmutableArray<ParameterSymb
if (parameter is { })
{
checkUnderspecifiedGenericExtension(parameter, ContainingType.TypeParameters, diagnostics);

TypeSymbol parameterType = parameter.TypeWithAnnotations.Type;
RefKind parameterRefKind = parameter.RefKind;
SyntaxNode? parameterTypeSyntax = parameterList.Parameters[0].Type;
Debug.Assert(parameterTypeSyntax is not null);

if (!parameterType.IsValidExtensionParameterType())
{
diagnostics.Add(ErrorCode.ERR_BadTypeforThis, parameterTypeSyntax, parameterType);
}
else if (parameterRefKind == RefKind.Ref && !parameterType.IsValueType)
{
diagnostics.Add(ErrorCode.ERR_RefExtensionParameterMustBeValueTypeOrConstrainedToOne, parameterTypeSyntax);
}
else if (parameterRefKind is RefKind.In or RefKind.RefReadOnlyParameter && parameterType.TypeKind != TypeKind.Struct)
{
diagnostics.Add(ErrorCode.ERR_InExtensionParameterMustBeValueType, parameterTypeSyntax);
}
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2025

Choose a reason for hiding this comment

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

I think it would be desirable to try sharing logic with ExtensionMethodChecks in order to avoid an accidental diverging #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree and I tried that initially, but the logic for these three checks in ExtensionMethodChecks uses locations and also the name of the method

Copy link
Member

Choose a reason for hiding this comment

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

Consider leaving a comment in both places saying that we should keep them in sync.


if (parameter.Name is "" && parameterRefKind != RefKind.None)
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 1, 2025

Choose a reason for hiding this comment

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

parameterRefKind != RefKind.None

Should scoped be disallowed as well? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, but that's currently a parsing error. See ReceiverParameterValidation_UnnamedReceiverParameter_Scoped, which has a comment for follow-up

{
diagnostics.Add(ErrorCode.ERR_ModifierOnUnnamedReceiverParameter, parameterTypeSyntax);
}
}

if (parameter is { Name: var name } && name != "" &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public FunctionPointerParameterSymbol(TypeWithAnnotations typeWithAnnotations, R
public override int Ordinal { get; }
public override Symbol ContainingSymbol => _containingSymbol;
public override ImmutableArray<CustomModifier> RefCustomModifiers { get; }
internal override bool HasEnumeratorCancellationAttribute => false;

internal override ScopedKind EffectiveScope
=> ParameterHelpers.IsRefScopedByDefault(this) ? ScopedKind.ScopedRef : ScopedKind.None;
Expand Down
11 changes: 11 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ internal static int GetMemberArityIncludingExtension(this Symbol member)
return member.GetMemberArity();
}

internal static ImmutableArray<ParameterSymbol> GetParametersIncludingExtensionParameter(this Symbol symbol)
{
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider optimizing
if (symbol.GetIsNewExtensionMember() && symbol.ContainingType.ExtensionParameter is { } extensionParameter)
{
return [extensionParameter, .. symbol.GetParameters()];
}

return symbol.GetParameters();
}

/// <summary>
/// For an extension member, we distribute the type arguments between the extension declaration and the member.
/// Otherwise, we just construct the member with the type arguments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,8 @@ public override ImmutableArray<CustomModifier> RefCustomModifiers
}
}

internal sealed override bool HasEnumeratorCancellationAttribute => throw ExceptionUtilities.Unreachable();

internal override bool IsMetadataIn
{
get { return (_flags & ParameterAttributes.In) != 0; }
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ protected sealed override Symbol OriginalSymbolDefinition
/// </summary>
public abstract ImmutableArray<CustomModifier> RefCustomModifiers { get; }

/// <summary>
/// Indicates whether the parameter has the EnumeratorCancellation attribute.
/// </summary>
internal abstract bool HasEnumeratorCancellationAttribute { get; }

/// <summary>
/// Describes how the parameter is marshalled when passed to native code.
/// Null if no specific marshalling information is available for the parameter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public SignatureOnlyParameterSymbol(

public override ImmutableArray<CustomModifier> RefCustomModifiers { get { return _refCustomModifiers; } }

internal override bool HasEnumeratorCancellationAttribute { get { throw ExceptionUtilities.Unreachable(); } }

public override bool IsParamsArray { get { return _isParamsArray; } }

public override bool IsParamsCollection { get { return _isParamsCollection; } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ public override ImmutableArray<CustomModifier> RefCustomModifiers
get { return _originalParam.RefCustomModifiers; }
}

internal sealed override bool HasEnumeratorCancellationAttribute
{
get { return _originalParam.HasEnumeratorCancellationAttribute; }
}

internal override MarshalPseudoCustomAttributeData MarshallingInformation
{
get { return _originalParam.MarshallingInformation; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ private static FlowAnalysisAnnotations DecodeFlowAnalysisAttributes(ParameterWel
internal override ImmutableHashSet<string> NotNullIfParameterNotNull
=> GetDecodedWellKnownAttributeData()?.NotNullIfParameterNotNull ?? ImmutableHashSet<string>.Empty;

internal bool HasEnumeratorCancellationAttribute
internal override bool HasEnumeratorCancellationAttribute
{
get
{
Expand Down
Loading
Loading