CA1045: Skicka inte typer efter referens
Property | Värde |
---|---|
Regel-ID | CA1045 |
Title | Skicka inte typer efter referens |
Kategori | Designa |
Korrigeringen är icke-bakåtkompatibel | Brott |
Aktiverad som standard i .NET 9 | Nej |
Orsak
En offentlig eller skyddad metod i en offentlig typ har en ref
parameter som tar en primitiv typ, en referenstyp eller en värdetyp som inte är en av de inbyggda typerna.
Regelbeskrivning
Att skicka typer efter referens (med hjälp out
av eller ref
) kräver erfarenhet av pekare, förståelse för hur värdetyper och referenstyper skiljer sig åt och hanteringsmetoder som har flera returvärden. Dessutom är skillnaden mellan out
och ref
parametrar inte allmänt förstådd.
När en referenstyp skickas "med referens" avser metoden att använda parametern för att returnera en annan instans av objektet. (Att skicka en referenstyp efter referens kallas även för att använda en dubbelpekare, pekare till en pekare eller dubbel indirektion.) Med standardanropskonventionen, som skickas "efter värde", tar en parameter som tar en referenstyp redan emot en pekare till objektet. Pekaren, inte det objekt som den pekar på, skickas av värdet. Att skicka efter värde innebär att metoden inte kan ändra pekaren så att den pekar på en ny instans av referenstypen, men kan ändra innehållet i det objekt som den pekar på. För de flesta program är detta tillräckligt och ger det beteende som du vill ha.
Om en metod måste returnera en annan instans använder du returvärdet för metoden för att åstadkomma detta. System.String Se klassen för en mängd olika metoder som fungerar på strängar och returnerar en ny instans av en sträng. Genom att använda den här modellen är det uppringaren som bestämmer om det ursprungliga objektet ska bevaras.
Även om returvärden är vanliga och används mycket, kräver rätt tillämpning av out
parametrar och ref
mellanliggande design- och kodningsfärdigheter. Biblioteksarkitekter som utformar för en allmän målgrupp bör inte förvänta sig att användarna ska bli skickliga på att arbeta med out
eller ref
parametrar.
Kommentar
När du arbetar med parametrar som är stora strukturer kan de ytterligare resurser som krävs för att kopiera dessa strukturer orsaka en prestandaeffekt när du skickar efter värde. I dessa fall kan du överväga att använda ref
eller out
parametrar.
Så här åtgärdar du överträdelser
Om du vill åtgärda en överträdelse av den här regeln som orsakas av en värdetyp måste metoden returnera objektet som dess returvärde. Om metoden måste returnera flera värden gör du om den för att returnera en enda instans av ett objekt som innehåller värdena.
Om du vill åtgärda en överträdelse av den här regeln som orsakas av en referenstyp kontrollerar du att det beteende som du vill ha är att returnera en ny instans av referensen. I så fall bör metoden använda sitt returvärde för att göra detta.
När du ska ignorera varningar
Det är säkert att ignorera en varning från den här regeln. Den här designen kan dock orsaka användbarhetsproblem.
Ignorera en varning
Om du bara vill förhindra en enda överträdelse lägger du till förprocessordirektiv i källfilen för att inaktivera och aktiverar sedan regeln igen.
#pragma warning disable CA1045
// The code that's violating the rule is on this line.
#pragma warning restore CA1045
Om du vill inaktivera regeln för en fil, mapp eller ett projekt anger du dess allvarlighetsgrad till none
i konfigurationsfilen.
[*.{cs,vb}]
dotnet_diagnostic.CA1045.severity = none
Mer information finns i Så här utelämnar du kodanalysvarningar.
Konfigurera kod för analys
Använd följande alternativ för att konfigurera vilka delar av kodbasen som regeln ska köras på.
Du kan konfigurera det här alternativet för bara den här regeln, för alla regler som gäller för eller för alla regler i den här kategorin (design) som den gäller för. Mer information finns i Konfigurationsalternativ för kodkvalitetsregel.
Inkludera specifika API-ytor
Du kan konfigurera vilka delar av kodbasen som ska köras med den här regeln baserat på deras tillgänglighet. Om du till exempel vill ange att regeln endast ska köras mot den icke-offentliga API-ytan lägger du till följande nyckel/värde-par i en .editorconfig-fil i projektet:
dotnet_code_quality.CAXXXX.api_surface = private, internal
Exempel 1
Följande bibliotek visar två implementeringar av en klass som genererar svar på feedback från användaren. Den första implementeringen (BadRefAndOut
) tvingar biblioteksanvändaren att hantera tre returvärden. Den andra implementeringen (RedesignedRefAndOut
) förenklar användarupplevelsen genom att returnera en instans av en containerklass (ReplyData
) som hanterar data som en enda enhet.
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
{
string reply;
Actions action;
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 { return reply; } }
public Actions Action { get { return action; } }
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;
}
}
Exempel 2
Följande program illustrerar användarens upplevelse. Anropet till det omdesignade biblioteket (UseTheSimplifiedClass
metoden) är enklare och den information som returneras av metoden hanteras enkelt. Utdata från de två metoderna är identiska.
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 Main1045()
{
UseTheComplicatedClass();
// Print a blank line in output.
Console.WriteLine("");
UseTheSimplifiedClass();
}
}
Exempel 3
Följande exempelbibliotek visar hur ref
parametrar för referenstyper används och visar ett bättre sätt att implementera den här funktionen.
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 = argument + " ABCDE";
}
// The following syntax will work, 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 = argument + " ABCDE";
}
// The following syntax will work 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";
}
}
Exempel 4
Följande program anropar varje metod i biblioteket för att demonstrera beteendet.
public class Test
{
public static void Main1045()
{
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);
}
}
Det här exemplet genererar följande utdata:
Changing pointer - passed by value:
12345
12345
Changing pointer - passed by reference:
12345
12345 ABCDE
Passing by return value:
12345 ABCDE