diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index 2b3888e09438ae1d01a0db7b2bb642a35044d4d0..2d13ee4d60a70d347ede6d398ceda3f95624e541 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -113,6 +113,8 @@ internal static class IDEDiagnosticIds public const string UseRecommendedDisposePatternDiagnosticId = "IDE0068"; public const string DisposableFieldsShouldBeDisposedDiagnosticId = "IDE0069"; + public const string UseSystemHashCode = "IDE0070"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index 2cf9f1dedc22e37cb63030516e574cc37693717d..3bab7b0df72efa824a64b16d9015276d88a06454 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -4617,6 +4617,15 @@ internal class FeaturesResources { } } + /// + /// Looks up a localized string similar to Use 'System.HashCode'. + /// + internal static string Use_System_HashCode { + get { + return ResourceManager.GetString("Use_System_HashCode", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use 'throw' expression. /// @@ -4636,7 +4645,7 @@ internal class FeaturesResources { } /// - /// Looks up a localized string similar to Use recommended dispose pattern to ensure that locally scoped disposable objects are disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transferred to another object [rest of string was truncated]";. + /// Looks up a localized string similar to Use recommended dispose pattern to ensure that locally scoped disposable objects are disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transferred to another object [rest of string was truncated]";. /// internal static string UseRecommendedDisposePatternDescription { get { diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index dfcc5c1931cd2cd9c033759788d48d5852f8ef80..39b4deebb430a5ad03a957151864dee2e222de14 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1711,4 +1711,7 @@ This version used in: {2} Add null checks for all parameters + + Use 'System.HashCode' + \ No newline at end of file diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs index b853478d475b2fcf85582108ed6c328a3d3973d9..2afdb14da99bfdf812d1b805ede41ebeb70c5570 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/AbstractGenerateEqualsAndGetHashCodeService.cs @@ -119,17 +119,15 @@ public async Task FormatDocumentAsync(Document document, CancellationT { var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var factory = document.GetLanguageService(); - return CreateGetHashCodeMethod( - factory, compilation, namedType, members, cancellationToken); + return CreateGetHashCodeMethod(factory, compilation, namedType, members); } private IMethodSymbol CreateGetHashCodeMethod( SyntaxGenerator factory, Compilation compilation, - INamedTypeSymbol namedType, ImmutableArray members, - CancellationToken cancellationToken) + INamedTypeSymbol namedType, ImmutableArray members) { var statements = CreateGetHashCodeStatements( - factory, compilation, namedType, members, cancellationToken); + factory, compilation, namedType, members); return CodeGenerationSymbolFactory.CreateMethodSymbol( attributes: default, @@ -146,15 +144,13 @@ public async Task FormatDocumentAsync(Document document, CancellationT private ImmutableArray CreateGetHashCodeStatements( SyntaxGenerator factory, Compilation compilation, - INamedTypeSymbol namedType, ImmutableArray members, - CancellationToken cancellationToken) + INamedTypeSymbol namedType, ImmutableArray members) { // If we have access to System.HashCode, then just use that. var hashCodeType = compilation.GetTypeByMetadataName("System.HashCode"); var components = factory.GetGetHashCodeComponents( - compilation, namedType, members, - justMemberReference: true, cancellationToken); + compilation, namedType, members, justMemberReference: true); if (components.Length > 0 && hashCodeType != null) { @@ -164,7 +160,7 @@ public async Task FormatDocumentAsync(Document document, CancellationT // Otherwise, try to just spit out a reasonable hash code for these members. var statements = factory.CreateGetHashCodeMethodStatements( - compilation, namedType, members, useInt64: false, cancellationToken); + compilation, namedType, members, useInt64: false); // Unfortunately, our 'reasonable' hash code may overflow in checked contexts. // C# can handle this by adding 'checked{}' around the code, VB has to jump @@ -198,7 +194,7 @@ public async Task FormatDocumentAsync(Document document, CancellationT // // This does mean all hashcodes will be positive. But it will avoid the overflow problem. return factory.CreateGetHashCodeMethodStatements( - compilation, namedType, members, useInt64: true, cancellationToken); + compilation, namedType, members, useInt64: true); } private ImmutableArray CreateGetHashCodeStatementsUsingSystemHashCode( diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index dbc960066a5847a2caa09b9dde1a719370e17d1b..1708ece006a4cf573ca73ff6e7f9493487af14ff 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -273,12 +273,8 @@ private bool IsStringCheck(IOperation condition, IParameterSymbol parameter) } private bool IsNullCheck(IOperation operand1, IOperation operand2, IParameterSymbol parameter) - => IsNullLiteral(UnwrapImplicitConversion(operand1)) && IsParameterReference(operand2, parameter); + => UnwrapImplicitConversion(operand1).IsNullLiteral() && IsParameterReference(operand2, parameter); - private bool IsNullLiteral(IOperation operand) - => operand is ILiteralOperation literal && - literal.ConstantValue.HasValue && - literal.ConstantValue.Value == null; private async Task AddNullCheckAsync( Document document, diff --git a/src/Features/Core/Portable/UseSystemHashCode/Analyzer.cs b/src/Features/Core/Portable/UseSystemHashCode/Analyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..414455c1e5031aa32fbdd7f66cd39367b7cb5a28 --- /dev/null +++ b/src/Features/Core/Portable/UseSystemHashCode/Analyzer.cs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.UseSystemHashCode +{ + internal struct Analyzer + { + private readonly IMethodSymbol _getHashCodeMethod; + private readonly IMethodSymbol _objectGetHashCodeMethod; + private readonly INamedTypeSymbol _containingType; + private readonly INamedTypeSymbol _equalityComparerTypeOpt; + + public Analyzer(IMethodSymbol getHashCodeMethod, IMethodSymbol objectGetHashCode, INamedTypeSymbol equalityComparerTypeOpt) + { + _getHashCodeMethod = getHashCodeMethod; + _containingType = _getHashCodeMethod.ContainingType.OriginalDefinition; + _objectGetHashCodeMethod = objectGetHashCode; + _equalityComparerTypeOpt = equalityComparerTypeOpt; + } + + public ImmutableArray GetHashedMembers(IBlockOperation blockOperation) + { + // Unwind through nested blocks. + while (blockOperation.Operations.Length == 1 && + blockOperation.Operations[0] is IBlockOperation childBlock) + { + blockOperation = childBlock; + } + + // Needs to be of the form: + // + // // accumulator + // var hashCode = + // + // // 1-N member hashes mixed into the accumulator. + // hashCode = (hashCode op constant) op member_hash + // + // // return of the value. + // return hashCode; + + var statements = blockOperation.Operations; + if (statements.Length == 0) + { + return default; + } + + var firstStatement = statements.First(); + var lastStatement = statements.Last(); + + if (!(firstStatement is IVariableDeclarationGroupOperation varDeclStatement) || + !(lastStatement is IReturnOperation returnStatement)) + { + return default; + } + + var variables = varDeclStatement.GetDeclaredVariables(); + if (variables.Length != 1) + { + return default; + } + + if (varDeclStatement.Declarations.Length != 1) + { + return default; + } + + var declaration = varDeclStatement.Declarations[0]; + if (declaration.Declarators.Length != 1) + { + return default; + } + + var declarator = declaration.Declarators[0]; + if (declarator.Initializer == null || + declarator.Initializer.Value == null) + { + return default; + } + + var accumulatorVariable = declarator.Symbol; + if (!(IsLocalReference(returnStatement.ReturnedValue, accumulatorVariable))) + { + return default; + } + + var initializerValue = declarator.Initializer.Value; + var hashedSymbols = ArrayBuilder.GetInstance(); + if (!IsLiteralNumber(initializerValue) && + !TryGetHashedSymbol(accumulatorVariable, hashedSymbols, initializerValue)) + { + return default; + } + + for (var i = 1; i < statements.Length - 1; i++) + { + var statement = statements[i]; + if (!(statement is IExpressionStatementOperation expressionStatement) || + !(expressionStatement.Operation is ISimpleAssignmentOperation simpleAssignment) || + !IsLocalReference(simpleAssignment.Target, accumulatorVariable) || + !TryGetHashedSymbol(accumulatorVariable, hashedSymbols, simpleAssignment.Value)) + { + return default; + } + } + + return hashedSymbols.ToImmutableAndFree(); + } + + private bool TryGetHashedSymbol( + ILocalSymbol accumulatorVariable, ArrayBuilder hashedSymbols, IOperation value) + { + value = Unwrap(value); + if (value is IInvocationOperation invocation) + { + var targetMethod = invocation.TargetMethod; + if (OverridesSystemObject(_objectGetHashCodeMethod, targetMethod)) + { + // (hashCode * -1521134295 + a.GetHashCode()).GetHashCode() + // recurse on the value we're calling GetHashCode on. + return TryGetHashedSymbol(accumulatorVariable, hashedSymbols, invocation.Instance); + } + + if (targetMethod.Name == nameof(GetHashCode) && + Equals(_equalityComparerTypeOpt, targetMethod.ContainingType.OriginalDefinition) && + invocation.Arguments.Length == 1) + { + // EqualityComparer.Default.GetHashCode(i) + return TryGetHashedSymbol(accumulatorVariable, hashedSymbols, invocation.Arguments[0].Value); + } + } + + // (hashCode op1 constant) op1 hashed_value + if (value is IBinaryOperation topBinary) + { + return topBinary.LeftOperand is IBinaryOperation leftBinary && + IsLocalReference(leftBinary.LeftOperand, accumulatorVariable) && + IsLiteralNumber(leftBinary.RightOperand) && + TryGetHashedSymbol(accumulatorVariable, hashedSymbols, topBinary.RightOperand); + } + + if (value is IInstanceReferenceOperation instanceReference) + { + // reference to this/base. + return instanceReference.ReferenceKind == InstanceReferenceKind.ContainingTypeInstance; + } + + if (value is IConditionalOperation conditional && + conditional.Condition is IBinaryOperation binary) + { + if (binary.RightOperand.IsNullLiteral() && + TryGetFieldOrProperty(binary.LeftOperand, out _)) + { + if (binary.OperatorKind == BinaryOperatorKind.Equals) + { + // (StringProperty == null ? 0 : StringProperty.GetHashCode()) + return TryGetHashedSymbol(accumulatorVariable, hashedSymbols, conditional.WhenFalse); + } + else if (binary.OperatorKind == BinaryOperatorKind.NotEquals) + { + // (StringProperty != null ? StringProperty.GetHashCode() : 0) + return TryGetHashedSymbol(accumulatorVariable, hashedSymbols, conditional.WhenTrue); + } + } + } + + if (TryGetFieldOrProperty(value, out var fieldOrProp) && + Equals(fieldOrProp.ContainingType.OriginalDefinition, _containingType)) + { + return Add(hashedSymbols, fieldOrProp); + } + + return false; + } + + private static bool TryGetFieldOrProperty(IOperation operation, out ISymbol symbol) + { + if (operation is IFieldReferenceOperation fieldReference) + { + symbol = fieldReference.Member; + return true; + } + + if (operation is IPropertyReferenceOperation propertyReference) + { + symbol = propertyReference.Member; + return true; + } + + symbol = null; + return false; + } + + private bool Add(ArrayBuilder hashedSymbols, ISymbol member) + { + foreach (var symbol in hashedSymbols) + { + if (Equals(symbol, member)) + { + return false; + } + } + + hashedSymbols.Add(member); + return true; + } + + private static bool IsLiteralNumber(IOperation value) + { + value = Unwrap(value); + return value is IUnaryOperation unary + ? unary.OperatorKind == UnaryOperatorKind.Minus && IsLiteralNumber(unary.Operand) + : value.IsNumericLiteral(); + } + + private static bool IsLocalReference(IOperation value, ILocalSymbol accumulatorVariable) + => Unwrap(value) is ILocalReferenceOperation localReference && accumulatorVariable.Equals(localReference.Local); + + private static IOperation Unwrap(IOperation value) + { + while (true) + { + if (value is IConversionOperation conversion) + { + value = conversion.Operand; + } + else if (value is IParenthesizedOperation parenthesized) + { + value = parenthesized.Operand; + } + else + { + return value; + } + } + } + + public static bool OverridesSystemObject(IMethodSymbol objectGetHashCode, IMethodSymbol method) + { + for (var current = method; current != null; current = current.OverriddenMethod) + { + if (objectGetHashCode.Equals(current)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Features/Core/Portable/UseSystemHashCode/UseSystemHashCodeDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseSystemHashCode/UseSystemHashCodeDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..a82b6c4ba090f0f6f0d83a9db79efb55af5ef447 --- /dev/null +++ b/src/Features/Core/Portable/UseSystemHashCode/UseSystemHashCodeDiagnosticAnalyzer.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Net.Mime; +using System.Text; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.UseSystemHashCode +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + internal class UseSystemHashCodeDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public UseSystemHashCodeDiagnosticAnalyzer() + : base(IDEDiagnosticIds.UseSystemHashCode, + CodeStyleOptions.PreferSystemHashCode, + new LocalizableResourceString(nameof(FeaturesResources.Use_System_HashCode), FeaturesResources.ResourceManager, typeof(FeaturesResources))) + { + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + { + context.RegisterCompilationStartAction(c => + { + // var hashCodeType = c.Compilation.GetTypeByMetadataName("System.HashCode"); + var objectType = c.Compilation.GetSpecialType(SpecialType.System_Object); + var objectGetHashCode = objectType?.GetMembers(nameof(GetHashCode)).FirstOrDefault() as IMethodSymbol; + var equalityComparerTypeOpt = c.Compilation.GetTypeByMetadataName(typeof(EqualityComparer<>).FullName); + + if (// hashCodeType != null && + objectGetHashCode != null) + { + c.RegisterOperationBlockAction(c2 => + AnalyzeOperationBlock(c2, objectGetHashCode, equalityComparerTypeOpt)); + } + }); + } + + private void AnalyzeOperationBlock( + OperationBlockAnalysisContext context, IMethodSymbol objectGetHashCode, INamedTypeSymbol equalityComparerTypeOpt) + { + if (!(context.OwningSymbol is IMethodSymbol method)) + { + return; + } + + if (method.Name != nameof(GetHashCode)) + { + return; + } + + if (!method.IsOverride) + { + return; + } + + if (method.Locations.Length != 1 || method.DeclaringSyntaxReferences.Length != 1) + { + return; + } + + var location = method.Locations[0]; + if (!location.IsInSource) + { + return; + } + + if (context.OperationBlocks.Length != 1) + { + return; + } + + var operation = context.OperationBlocks[0]; + if (!(operation is IBlockOperation blockOperation)) + { + return; + } + + if (!Analyzer.OverridesSystemObject(objectGetHashCode, method)) + { + return; + } + + var cancellationToken = context.CancellationToken; + + var optionSet = context.Options.GetDocumentOptionSetAsync(location.SourceTree, cancellationToken).GetAwaiter().GetResult(); + if (optionSet == null) + { + return; + } + + var option = optionSet.GetOption(CodeStyleOptions.PreferSystemHashCode, operation.Language); + if (!option.Value) + { + return; + } + + var analyzer = new Analyzer(method, objectGetHashCode, equalityComparerTypeOpt); + var hashedMembers = analyzer.GetHashedMembers(blockOperation); + + if (!hashedMembers.IsDefaultOrEmpty) + { + context.ReportDiagnostic(DiagnosticHelper.Create( + this.Descriptor, location, option.Notification.Severity, + new[] { operation.Syntax.GetLocation() }, ImmutableDictionary.Empty)); + } + } + } +} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 312c1a842a79687fdf781dbd8a62a6273e28b812..6a6ecb3804ae7dca32fe625d400b2dc29962f707 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -542,6 +542,11 @@ Použijte doporučený vzor vyřazení, abyste měli jistotu, že objekty, které lze vyřadit v místním oboru, se vyřadí na všech cestách. Pokud je to možné, zabalte vytváření do příkazu nebo deklarace using. Jinak použijte vzor try-finally s vyhrazenou místní proměnnou deklarovanou před oblastí try a nepodmíněným voláním Dispose pro hodnotu, která není null, v oblasti finally, třeba x?.Dispose(). Pokud se objekt explicitně vyřadí v oblasti try nebo se vlastnictví vyřazení převede na jiný objekt nebo metodu, přiřaďte ihned po takové operaci místní proměnné hodnotu null, aby ve finally nedošlo k dvojímu vyřazení. + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Pro lambda výrazy používat text bloku diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index f39f9cacb6660222d03c2cb127081c338f623785..913a1b731d8cc2b82372f28971fcaa4d95e8ba43 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -542,6 +542,11 @@ Verwenden Sie das empfohlene Dispose-Muster, um sicherzustellen, dass löschbare Objekte für den lokalen Bereich in allen Pfaden gelöscht werden. Schließen Sie die Erstellung nach Möglichkeit in einer using-Anweisung oder einer using-Deklaration ein. Verwenden Sie andernfalls ein try-finally-Muster mit einer vor dem try-Bereich deklarierten dedizierten lokalen Variablen und einem Dispose-Aufruf ohne Bedingung für den Nicht-NULL-Wert im finally-Bereich, beispielsweise "x?.Dispose()". Wenn das Objekt explizit innerhalb des try-Bereichs gelöscht oder der Dispose-Besitz auf ein anderes Objekt oder eine andere Methode übertragen wird, weisen Sie der lokalen Variablen gleich nach einem solchen Vorgang NULL zu, um einen doppelten Löschvorgang in "finally" zu vermeiden. + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Blockkörper für Lambdaausdrücke verwenden diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 23548abe92589cac8def8e8933b661800c5bb55d..c3d192cf2d5c73325ab3fde1f2b950f2e91f5690 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -542,6 +542,11 @@ Use el patrón Dispose recomendado para asegurarse de que los objetos descartables de ámbito local se desechan en todas las rutas de acceso. Si es posible, incluya la creación en una instrucción "using" o una declaración "using". En caso contrario, use un patrón try-finally, con la declaración de una variable local dedicada antes de la región "try" y una invocación de Dispose incondicional en un valor no nulo en la región "finally", por ejemplo, "x?.Dispose()". Si el objeto se desecha de forma explícita en la región "try" o la pertenencia de Dispose se transfiere a otro objeto o método, asigne "null" a la variable local justo después de tal operación para evitar un doble Dispose en "finally". + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Usar cuerpo de bloque de expresiones lambda diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index daca22e8740a122a55b7ea5faca83adee5922333..7752a395c849e447a3527e6a207ab801677a0187 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -542,6 +542,11 @@ Utilisez le modèle de suppression recommandé pour vérifier que les objets supprimables de l'étendue locale sont bien supprimés sur tous les chemins. Si possible, incluez la création dans un wrapper au sein d'une instruction 'using' ou d'une déclaration 'using'. Sinon, utilisez un modèle try-finally avec une variable locale dédiée déclarée avant la région try et un appel inconditionnel de Dispose sur une valeur non null dans la région 'finally', par exemple 'x?.Dispose()'. Si l'objet est explicitement supprimé dans la région try ou si la propriété de suppression est transférée vers un autre objet ou une autre méthode, affectez la valeur 'null' à la variable locale juste après cette opération pour éviter une double suppression dans 'finally' + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Utiliser le corps de bloc pour les expressions lambda diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 6eaa4004d0a678d8a36147b83a1dd84fe42fa6a8..3b2b1fd10027d5fc1d8a7e58eedee8cd1ac7e96e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -542,6 +542,11 @@ Usare il criterio dispose consigliato per garantire che gli oggetti eliminabili con ambito locale vengano eliminati in tutti i percorsi. Se possibile, eseguire il wrapping della creazione in un'istruzione 'using' o una dichiarazione 'using'. In caso contrario, usare un criterio try-finally, con una variabile locale dedicata dichiarata prima dell'area try e una chiamata Dispose non condizionale al valore non Null nell'area 'finally', ad esempio 'x?.Dispose()'. Se l'oggetto viene eliminato in modo esplicito nell'area try oppure la proprietà di dispose viene trasferita a un altro oggetto o metodo, assegnare 'null' alla variabile locale subito dopo una tale operazione per evitare di raddoppiare dispose in 'finally'. + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Usa il corpo del blocco per le espressioni lambda diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index b80284dd29f8f14768e28940c7251561b0ad53b8..d37a87154ef0d049832020c8336e3570592ec652 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -542,6 +542,11 @@ 推奨される dispose パターンを使用して、ローカル スコープの破棄可能なオブジェクトがすべてのパスで破棄されるようにします。可能なら、'using' ステートメントまたは 'using' 宣言内で作成をラップします。または、try-finally パターンを、try 領域の前で宣言された専用のローカル変数と、'finally' 領域の非 null 値での条件なしの Dispose の呼び出し (例: 'x?.Dispose()') とともに使用します。オブジェクトが try 領域内で明示的に破棄されるか、dispose の所有権が他のオブジェクトまたはメソッドに移される場合、その操作のすぐ後で 'null' をローカル変数に割り当てて、'finally' 内での dispose の重複を回避します + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions ラムダ式にブロック本体を使用する diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index b934e00fdcd063f27ec58d9f074e9c91b50dfd58..cda29b7f79d4232fcfdf44e2dc510218bcdf3675 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -542,6 +542,11 @@ 권장 dispose 패턴을 사용하여 로컬로 범위가 지정된 삭제 가능한 개체가 모든 경로에서 삭제되도록 합니다. 가능한 경우 'using' 문이나 'using' 선언 내에서 생성을 래핑합니다. 그러지 않으면 try 영역 앞에 선언된 전용 지역 변수 및 'finally' 영역에 있는 null이 아닌 값의 비조건부 Dispose 호출('x?.Dispose()')과 함께 try-finally 패턴을 사용하세요. 개체가 try 영역 내에서 명시적으로 삭제되거나 삭제 소유권이 다른 개체나 메서드로 이전되면 해당 작업 바로 뒤의 지역 변수에 'null'을 할당하여 'finally'에서 이중 삭제를 방지하세요. + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions 람다 식에 블록 본문 사용 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 97c6657c748a0db78df3a163416aec6cd7fdf9cb..adfc3a1ac6446d7df544737d1766f55a78c52638 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -542,6 +542,11 @@ Użyj zalecanego wzorca dispose, aby upewnić się, że obiekty możliwe do likwidacji w lokalnym zakresie są likwidowane we wszystkich ścieżkach. Jeśli to możliwe, opakuj tworzenie w instrukcji „using” lub deklaracji „using”. W przeciwnym razie użyj wzorca try-finally, z dedykowaną zmienną lokalną zadeklarowaną przed regionem try i bezwarunkowym wywołaniem metody Dispose dla wartości innej niż null w regionie „finally”, na przykład „x?.Dispose()”. Jeśli obiekt jest jawnie likwidowany w regionie try lub własność dispose jest przenoszona do innego obiektu lub metody, przypisz wartość „null” do zmiennej lokalnej zaraz po takiej operacji, aby zapobiec podwójnemu wywołaniu dispose w regionie „finally”. + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Użyj treści bloku dla wyrażeń lambda diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index a374c839446f82bce3b32b210282d2655a0bcc5f..14a8a263ba697a919c439f066b429512efc46990 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -542,6 +542,11 @@ Use o padrão de descarte recomendado para garantir que os objetos descartáveis no escopo local sejam descartados em todos os caminhos. Se possível, encapsule a criação em uma instrução 'using' ou em uma declaração 'using'. Caso contrário, use um padrão try-finally, com uma variável local dedicada declarada antes da região try e uma invocação de Dispose incondicional em um valor que não seja nulo na região 'finally', como x?.Dispose()'. Se o objeto for descartado explicitamente dentro da região try ou se a propriedade de descarte for transferida para outro objeto ou método, atribua 'null' à variável local logo após essa operação para evitar um descarte duplo em 'finally' + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Usar o corpo do bloco para expressões lambda diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 89ee62371a3b996305d350dfa9fe99e2533b6905..3903d46b69592ee81fd70be8f7e66270fab2c63a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -542,6 +542,11 @@ Используйте рекомендуемый шаблон Dispose, чтобы убедиться, что все освобождаемые объекты в локальной области освобождены для всех путей. Если это возможно, заключите создание объекта в оператор "using" или в объявление "using". В противном случае используйте шаблон try-finally с выделенной локальной переменной, объявляемой до области try, и безусловным вызовом Dispose для отличного от NULL значения в области "finally", например "x?.Dispose()". Если объект явно освобождается в области try или если права владения для операции освобождения передаются другому объекту или методу, присвойте значение "null" локальной переменной сразу после такой операции, чтобы предотвратить двойное освобождение в области "finally". + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Использовать тело блока для лямбда-выражений diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 8817a490f5473bcd1503ff2ca87d1439715b6bcc..b6253f23b764ddd2d9e006bb5ae7e75d45385332 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -542,6 +542,11 @@ Yerel olarak kapsamı oluşturulan nesnelerin tüm yollarda atıldığından emin olmak için önerilen atma desenini kullanın. Mümkünse, oluşturulan nesneyi 'using' deyimi veya 'using' bildirimiyle sarmalayın. Aksi halde, try bölgesinden önce bildirilen ayrılmış bir yerel değişkeni ve 'finally' bölgesinde null olmayan değer üzerinde koşulsuz bir Dispose çağrısı (örneğin, 'x?.Dispose()') olan bir try-finally deseni kullanın. Nesne try bölgesi içinde açıkça atıldıysa veya atma sahipliği başka bir nesne ya da metoda aktarıldıysa, 'finally' bölgesinde çift atma gerçekleşmesini önlemek için bu tür bir işlemden hemen sonra yerel değişkene 'null' atayın + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions Lambda ifadeleri için blok vücut kullanımı diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index f2ec672ef215aee336f8068346c8f63ebc164642..79ebf147bb1dcc41c641de6d5b31de060a95dfd4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -542,6 +542,11 @@ 使用推荐的 Dispose 模式以确保在所有路径中释放局部可释放对象。如果可能,请将创建包装在 "using" 语句或 "using" 声明中。否则,请使用 try-finally 模式,在 try 区域之前声明一个专用的局部变量,在 "finally" 区域中对非 null 值进行无条件 Dispose 调用,比如,"x?.Dispose()"。如果对象显式释放在 try 区域内或释放所有权转让给另一个对象或方法,则在这样的操作之后立即将 "null" 分配给局部变量,以防止在 "finally" 中进行双重释放。 + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions 对 lambda 表达式使用块正文 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index f71d1d47469da206feee1a8dd356f1aa3f5c4407..b3b0bc1338f57a46167b2b8dc717adbc076460a3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -542,6 +542,11 @@ 請使用建議的處置模式,確保區域範圍的可處置物件在所有路徑上均會經過處置。在可能的情況下,請將建立包在 'using' 陳述式或 'using' 宣告內。否則,請使用 try-finally 模式,同時在 try 區域之前先宣告專用的區域變數,並在 'finally' 區域中的非 null 值上,設定無條件 Dispose 引動過程,比如 'x?.Dispose()'。如果 try 區域內已明確地處置了該物件,或是處置擁有權已轉移到另一個物件或方法,則請在這類作業之後,對區域變數指派 'null',以避免在 'finally' 中發生雙重處置 + + Use 'System.HashCode' + Use 'System.HashCode' + + Use block body for lambda expressions 使用 Lambda 運算式的區體主體 diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs index 32eca61f323e250fb1934a842c88a18d4be92ed0..cf1ef41c07e5b3459f7cd9a0226804742b9efc2d 100644 --- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs +++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs @@ -358,6 +358,13 @@ private static string GetAccessibilityModifiersRequiredEditorConfigString(CodeSt KeyValuePairUtil.Create("all", UnusedParametersPreference.AllMethods), }); + internal static readonly PerLanguageOption> PreferSystemHashCode = new PerLanguageOption>( + nameof(CodeStyleOptions), + nameof(PreferSystemHashCode), + defaultValue: TrueWithSuggestionEnforcement, + storageLocations: new OptionStorageLocation[]{ + new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferSystemHashCode") }); + static CodeStyleOptions() { // Note that the static constructor executes after all the static field initializers for the options have executed, diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/OperationExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/OperationExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..f3701b8a18e86ea203b483efe422d2e5f46d15b7 --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Extensions/OperationExtensions.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.CodeAnalysis.Shared.Extensions +{ + internal static class OperationExtensions + { + public static bool IsNumericLiteral(this IOperation operation) + => operation.Kind == OperationKind.Literal && operation.Type.IsNumericType(); + + public static bool IsNullLiteral(this IOperation operand) + => operand is ILiteralOperation literal && + literal.ConstantValue.HasValue && + literal.ConstantValue.Value == null; + } +} diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs index 01885d090f0b948d0376109d02e19ecb4a6449eb..132f00f93b679fd89af1d979cbb959cf29ca7278 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateGetHashCodeMethod.cs @@ -18,8 +18,7 @@ internal static partial class SyntaxGeneratorExtensions Compilation compilation, INamedTypeSymbol containingType, ImmutableArray members, - bool justMemberReference, - CancellationToken cancellationToken) + bool justMemberReference) { var result = ArrayBuilder.GetInstance(); @@ -46,11 +45,10 @@ internal static partial class SyntaxGeneratorExtensions Compilation compilation, INamedTypeSymbol containingType, ImmutableArray members, - bool useInt64, - CancellationToken cancellationToken) + bool useInt64) { var components = GetGetHashCodeComponents( - factory, compilation, containingType, members, justMemberReference: false, cancellationToken); + factory, compilation, containingType, members, justMemberReference: false); if (components.Length == 0) { diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_Negate.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_Negate.cs index 40b212b80d375d7346d21a99c86aa8e7d2c64645..67ac53a83053c5e27993f7eceba904f15957f84e 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_Negate.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_Negate.cs @@ -197,10 +197,10 @@ internal static partial class SyntaxGeneratorExtensions switch (operationKind) { - case BinaryOperatorKind.LessThanOrEqual when IsNumericLiteral(rightOperand): + case BinaryOperatorKind.LessThanOrEqual when rightOperand.IsNumericLiteral(): return CanSimplifyToLengthEqualsZeroExpression( leftOperand, (ILiteralOperation)rightOperand); - case BinaryOperatorKind.GreaterThanOrEqual when IsNumericLiteral(leftOperand): + case BinaryOperatorKind.GreaterThanOrEqual when leftOperand.IsNumericLiteral(): return CanSimplifyToLengthEqualsZeroExpression( rightOperand, (ILiteralOperation)leftOperand); } @@ -208,9 +208,6 @@ internal static partial class SyntaxGeneratorExtensions return false; } - private static bool IsNumericLiteral(IOperation operation) - => operation.Kind == OperationKind.Literal && operation.Type.IsNumericType(); - private static IOperation RemoveImplicitConversion(IOperation operation) { return operation is IConversionOperation conversion && conversion.IsImplicit