CA1021: Unikanie parametrów wyjściowych
Właściwości | Wartość |
---|---|
Identyfikator reguły | CA1021 |
Tytuł | Unikaj parametrów out |
Kategoria | Projekt |
Poprawka powodująca niezgodność lub niezgodność | Kluczowa |
Domyślnie włączone na platformie .NET 9 | Nie. |
Przyczyna
Publiczna lub chroniona metoda w typie publicznym ma out
parametr .
Domyślnie ta reguła analizuje tylko typy widoczne zewnętrznie, ale można to skonfigurować.
Opis reguły
Przekazywanie typów według odwołania (przy użyciu out
lub ref
) wymaga doświadczenia ze wskaźnikami, zrozumienia różnic typów wartości i typów referencyjnych oraz obsługi metod z wieloma wartościami zwracanymi. Ponadto różnica między parametrami out
i ref
nie jest powszechnie rozumiana.
Gdy typ odwołania jest przekazywany przez odwołanie, metoda zamierza użyć parametru w celu zwrócenia innego wystąpienia obiektu. Przekazywanie typu odwołania według odwołania jest również nazywane użyciem podwójnego wskaźnika, wskaźnika do wskaźnika lub podwójnej pośrednii. Używając domyślnej konwencji wywoływania, która jest przekazywana "według wartości", parametr, który przyjmuje typ odwołania, otrzymuje już wskaźnik do obiektu. Wskaźnik, a nie obiekt, do którego wskazuje, jest przekazywany przez wartość. Przekazywanie według wartości oznacza, że metoda nie może zmienić wskaźnika, aby wskazywała nowe wystąpienie typu odwołania. Może jednak zmienić zawartość obiektu, na który wskazuje. W przypadku większości aplikacji jest to wystarczające i daje pożądane zachowanie.
Jeśli metoda musi zwrócić inne wystąpienie, użyj wartości zwracanej przez metodę , aby to osiągnąć. Zobacz klasę System.String dla różnych metod, które działają na ciągach i zwracają nowe wystąpienie ciągu. Gdy ten model jest używany, obiekt wywołujący musi zdecydować, czy oryginalny obiekt jest zachowany.
Chociaż wartości zwracane są powszechnie używane i intensywnie używane, prawidłowe zastosowanie parametrów out
i ref
wymaga pośrednich umiejętności projektowania i kodowania. Architekci bibliotek, którzy projektują dla odbiorców ogólnych, nie powinni oczekiwać, że użytkownicy staną się biegłi w pracy z parametrami out
lub ref
.
Jak naprawić naruszenia
Aby naprawić naruszenie tej reguły, która jest spowodowana przez typ wartości, metoda zwraca obiekt jako wartość zwracaną. Jeśli metoda musi zwracać wiele wartości, przeprojektuj ją, aby zwrócić pojedyncze wystąpienie obiektu, który przechowuje wartości.
Aby naprawić naruszenie tej reguły spowodowane przez typ odwołania, upewnij się, że żądane zachowanie ma zwrócić nowe wystąpienie odwołania. Jeśli tak jest, metoda powinna użyć jej wartości zwracanej, aby to zrobić.
Kiedy pomijać ostrzeżenia
Można bezpiecznie pominąć ostrzeżenie z tej reguły. Jednak ten projekt może powodować problemy z użytecznością.
Pomijanie ostrzeżenia
Jeśli chcesz po prostu pominąć pojedyncze naruszenie, dodaj dyrektywy preprocesora do pliku źródłowego, aby wyłączyć, a następnie ponownie włączyć regułę.
#pragma warning disable CA1021
// The code that's violating the rule is on this line.
#pragma warning restore CA1021
Aby wyłączyć regułę dla pliku, folderu lub projektu, ustaw jego ważność na none
w pliku konfiguracji.
[*.{cs,vb}]
dotnet_diagnostic.CA1021.severity = none
Aby uzyskać więcej informacji, zobacz Jak pominąć ostrzeżenia dotyczące analizy kodu.
Konfigurowanie kodu do analizowania
Użyj następującej opcji, aby skonfigurować, które części bazy kodu mają być uruchamiane w tej regule.
Tę opcję można skonfigurować tylko dla tej reguły, dla wszystkich reguł, do których ma ona zastosowanie, lub dla wszystkich reguł w tej kategorii (Projekt), których dotyczy. Aby uzyskać więcej informacji, zobacz Opcje konfiguracji reguły jakości kodu.
Uwzględnij określone powierzchnie interfejsu API
Możesz skonfigurować, na których częściach bazy kodu ma być uruchamiana ta reguła, na podstawie ich ułatwień dostępu. Aby na przykład określić, że reguła powinna być uruchamiana tylko na powierzchni niepublicznego interfejsu API, dodaj następującą parę klucz-wartość do pliku editorconfig w projekcie:
dotnet_code_quality.CAXXXX.api_surface = private, internal
Przykład 1
Poniższa biblioteka przedstawia dwie implementacje klasy, która generuje odpowiedzi na opinie użytkowników. Pierwsza implementacja (BadRefAndOut
) wymusza, aby użytkownik biblioteki zarządzał trzema zwracanymi wartościami. Druga implementacja (RedesignedRefAndOut
) upraszcza środowisko użytkownika, zwracając wystąpienie klasy kontenera (ReplyData
), które zarządza danymi jako pojedynczą jednostką.
public enum Actions
{
Unknown,
Discard,
ForwardToManagement,
ForwardToDeveloper
}
public enum TypeOfFeedback
{
Complaint,
Praise,
Suggestion,
Incomprehensible
}
public class BadRefAndOut
{
// Violates rule: DoNotPassTypesByReference.
public static bool ReplyInformation(TypeOfFeedback input,
out string reply, ref Actions action)
{
bool returnReply = false;
string replyText = "Your feedback has been forwarded " +
"to the product manager.";
reply = String.Empty;
switch (input)
{
case TypeOfFeedback.Complaint:
case TypeOfFeedback.Praise:
action = Actions.ForwardToManagement;
reply = "Thank you. " + replyText;
returnReply = true;
break;
case TypeOfFeedback.Suggestion:
action = Actions.ForwardToDeveloper;
reply = replyText;
returnReply = true;
break;
case TypeOfFeedback.Incomprehensible:
default:
action = Actions.Discard;
returnReply = false;
break;
}
return returnReply;
}
}
// Redesigned version does not use out or ref parameters.
// Instead, it returns this container type.
public class ReplyData
{
bool _returnReply;
// Constructors.
public ReplyData()
{
this.Reply = String.Empty;
this.Action = Actions.Discard;
this._returnReply = false;
}
public ReplyData(Actions action, string reply, bool returnReply)
{
this.Reply = reply;
this.Action = action;
this._returnReply = returnReply;
}
// Properties.
public string Reply { get; }
public Actions Action { get; }
public override string ToString()
{
return String.Format("Reply: {0} Action: {1} return? {2}",
Reply, Action.ToString(), _returnReply.ToString());
}
}
public class RedesignedRefAndOut
{
public static ReplyData ReplyInformation(TypeOfFeedback input)
{
ReplyData answer;
string replyText = "Your feedback has been forwarded " +
"to the product manager.";
switch (input)
{
case TypeOfFeedback.Complaint:
case TypeOfFeedback.Praise:
answer = new ReplyData(
Actions.ForwardToManagement,
"Thank you. " + replyText,
true);
break;
case TypeOfFeedback.Suggestion:
answer = new ReplyData(
Actions.ForwardToDeveloper,
replyText,
true);
break;
case TypeOfFeedback.Incomprehensible:
default:
answer = new ReplyData();
break;
}
return answer;
}
}
Przykład 2
Poniższa aplikacja ilustruje środowisko użytkownika. Wywołanie przeprojektowanej biblioteki (UseTheSimplifiedClass
metody) jest prostsze, a informacje zwracane przez metodę są łatwo zarządzane. Dane wyjściowe z dwóch metod są identyczne.
public class UseComplexMethod
{
static void UseTheComplicatedClass()
{
// Using the version with the ref and out parameters.
// You do not have to initialize an out parameter.
string[] reply = new string[5];
// You must initialize a ref parameter.
Actions[] action = {Actions.Unknown,Actions.Unknown,
Actions.Unknown,Actions.Unknown,
Actions.Unknown,Actions.Unknown};
bool[] disposition = new bool[5];
int i = 0;
foreach (TypeOfFeedback t in Enum.GetValues(typeof(TypeOfFeedback)))
{
// The call to the library.
disposition[i] = BadRefAndOut.ReplyInformation(
t, out reply[i], ref action[i]);
Console.WriteLine("Reply: {0} Action: {1} return? {2} ",
reply[i], action[i], disposition[i]);
i++;
}
}
static void UseTheSimplifiedClass()
{
ReplyData[] answer = new ReplyData[5];
int i = 0;
foreach (TypeOfFeedback t in Enum.GetValues(typeof(TypeOfFeedback)))
{
// The call to the library.
answer[i] = RedesignedRefAndOut.ReplyInformation(t);
Console.WriteLine(answer[i++]);
}
}
public static void UseClasses()
{
UseTheComplicatedClass();
// Print a blank line in output.
Console.WriteLine("");
UseTheSimplifiedClass();
}
}
Przykład 3
Poniższa przykładowa biblioteka ilustruje sposób ref
użycia parametrów typów referencyjnych i przedstawia lepszy sposób implementacji tej funkcji.
public class ReferenceTypesAndParameters
{
// The following syntax will not work. You cannot make a
// reference type that is passed by value point to a new
// instance. This needs the ref keyword.
public static void BadPassTheObject(string argument)
{
argument += " ABCDE";
}
// The following syntax works, but is considered bad design.
// It reassigns the argument to point to a new instance of string.
// Violates rule DoNotPassTypesByReference.
public static void PassTheReference(ref string argument)
{
argument += " ABCDE";
}
// The following syntax works and is a better design.
// It returns the altered argument as a new instance of string.
public static string BetterThanPassTheReference(string argument)
{
return argument + " ABCDE";
}
}
Przykład 4
Poniższa aplikacja wywołuje każdą metodę w bibliotece, aby zademonstrować zachowanie.
public class Test
{
public static void MainTest()
{
string s1 = "12345";
string s2 = "12345";
string s3 = "12345";
Console.WriteLine("Changing pointer - passed by value:");
Console.WriteLine(s1);
ReferenceTypesAndParameters.BadPassTheObject(s1);
Console.WriteLine(s1);
Console.WriteLine("Changing pointer - passed by reference:");
Console.WriteLine(s2);
ReferenceTypesAndParameters.PassTheReference(ref s2);
Console.WriteLine(s2);
Console.WriteLine("Passing by return value:");
s3 = ReferenceTypesAndParameters.BetterThanPassTheReference(s3);
Console.WriteLine(s3);
}
}
Ten przykład generuje następujące wyniki:
Changing pointer - passed by value:
12345
12345
Changing pointer - passed by reference:
12345
12345 ABCDE
Passing by return value:
12345 ABCDE
Wypróbuj metody wzorca
Metody implementujące wzorzec Wypróbuj<coś> , na przykład System.Int32.TryParse, nie zgłaszają tego naruszenia. W poniższym przykładzie przedstawiono strukturę (typ wartości), która implementuje metodę System.Int32.TryParse .
public struct Point
{
public Point(int axisX, int axisY)
{
X = axisX;
Y = axisY;
}
public int X { get; }
public int Y { get; }
public override int GetHashCode()
{
return X ^ Y;
}
public override bool Equals(object? obj)
{
if (!(obj is Point))
return false;
return Equals((Point)obj);
}
public bool Equals(Point other)
{
if (X != other.X)
return false;
return Y == other.Y;
}
public static bool operator ==(Point point1, Point point2)
{
return point1.Equals(point2);
}
public static bool operator !=(Point point1, Point point2)
{
return !point1.Equals(point2);
}
// Does not violate this rule
public static bool TryParse(string value, out Point result)
{
// TryParse Implementation
result = new Point(0, 0);
return false;
}
}