Delen via


CA1021: Parameters vermijden

Eigenschappen Weergegeven als
Regel-id CA1021
Titel Vermijd parameters
Categorie Ontwerpen
Oplossing is brekend of niet-brekend Breken
Standaard ingeschakeld in .NET 9 Nee

Oorzaak

Een openbare of beveiligde methode in een openbaar type heeft een out parameter.

Deze regel kijkt standaard alleen naar extern zichtbare typen, maar dit kan worden geconfigureerd.

Beschrijving van regel

Het doorgeven van typen per verwijzing (met of out ref) vereist ervaring met aanwijzers, inzicht in hoe waardetypen en verwijzingstypen verschillen en methoden verwerken met meerdere retourwaarden. Ook wordt het verschil tussen out en ref parameters niet algemeen begrepen.

Wanneer een verwijzingstype wordt doorgegeven doorverwijzing, is de methode van plan de parameter te gebruiken om een ander exemplaar van het object te retourneren. Het doorgeven van een verwijzingstype wordt ook wel aangeduid als het gebruik van een dubbele aanwijzer, aanwijzer naar een aanwijzer of dubbele indirectie. Door de standaardconventie voor aanroepen te gebruiken, die wordt doorgegeven door 'op waarde', ontvangt een parameter die een verwijzingstype gebruikt al een aanwijzer naar het object. De aanwijzer, niet het object waarnaar het verwijst, wordt doorgegeven door een waarde. Pass by value betekent dat de methode de aanwijzer niet kan wijzigen zodat deze verwijst naar een nieuw exemplaar van het referentietype. De inhoud van het object waarnaar het verwijst, kan echter worden gewijzigd. Voor de meeste toepassingen is dit voldoende en levert dit het gewenste gedrag op.

Als een methode een ander exemplaar moet retourneren, gebruikt u de retourwaarde van de methode om dit te bereiken. Zie de System.String klasse voor verschillende methoden die op tekenreeksen werken en een nieuw exemplaar van een tekenreeks retourneren. Wanneer dit model wordt gebruikt, moet de aanroeper beslissen of het oorspronkelijke object behouden blijft.

Hoewel retourwaarden gebruikelijk en intensief worden gebruikt, is voor de juiste toepassing van out en ref parameters tussenliggende ontwerp- en coderingsvaardigheden vereist. Bibliotheekarchitecten die voor een algemeen publiek ontwerpen, mogen niet verwachten dat gebruikers bekwaam worden in het werken met out of ref parameters.

Schendingen oplossen

Als u een schending van deze regel wilt oplossen die wordt veroorzaakt door een waardetype, moet u ervoor zorgen dat de methode het object retourneert als retourwaarde. Als de methode meerdere waarden moet retourneren, ontwerpt u deze opnieuw om één exemplaar te retourneren van een object dat de waarden bevat.

Als u een schending van deze regel wilt oplossen die wordt veroorzaakt door een verwijzingstype, moet u ervoor zorgen dat het gewenste gedrag een nieuw exemplaar van de verwijzing retourneert. Als dit het geval is, moet de methode de retourwaarde gebruiken om dit te doen.

Wanneer waarschuwingen onderdrukken

Het is veilig om een waarschuwing van deze regel te onderdrukken. Dit ontwerp kan echter bruikbaarheidsproblemen veroorzaken.

Een waarschuwing onderdrukken

Als u slechts één schending wilt onderdrukken, voegt u preprocessorrichtlijnen toe aan uw bronbestand om de regel uit te schakelen en vervolgens opnieuw in te schakelen.

#pragma warning disable CA1021
// The code that's violating the rule is on this line.
#pragma warning restore CA1021

Als u de regel voor een bestand, map of project wilt uitschakelen, stelt u de ernst none ervan in op het configuratiebestand.

[*.{cs,vb}]
dotnet_diagnostic.CA1021.severity = none

Zie Codeanalysewaarschuwingen onderdrukken voor meer informatie.

Code configureren om te analyseren

Gebruik de volgende optie om te configureren op welke onderdelen van uw codebase deze regel moet worden uitgevoerd.

U kunt deze optie configureren voor alleen deze regel, voor alle regels waarop deze van toepassing is, of voor alle regels in deze categorie (ontwerp) waarop deze van toepassing is. Zie de configuratieopties voor de codekwaliteitsregel voor meer informatie.

Specifieke API-oppervlakken opnemen

U kunt instellen op welke onderdelen van uw codebase deze regel moet worden uitgevoerd, op basis van hun toegankelijkheid. Als u bijvoorbeeld wilt opgeven dat de regel alleen moet worden uitgevoerd op het niet-openbare API-oppervlak, voegt u het volgende sleutel-waardepaar toe aan een .editorconfig-bestand in uw project:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Voorbeeld 1

In de volgende bibliotheek ziet u twee implementaties van een klasse waarmee reacties op feedback van gebruikers worden gegenereerd. De eerste implementatie (BadRefAndOut) dwingt de bibliotheekgebruiker drie retourwaarden te beheren. De tweede implementatie (RedesignedRefAndOut) vereenvoudigt de gebruikerservaring door een exemplaar van een containerklasse (ReplyData) te retourneren dat de gegevens als één eenheid beheert.

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;
    }
}

Voorbeeld 2

De volgende toepassing illustreert de ervaring van de gebruiker. De aanroep van de opnieuw ontworpen bibliotheek (UseTheSimplifiedClass methode) is eenvoudiger en de informatie die door de methode wordt geretourneerd, wordt eenvoudig beheerd. De uitvoer van de twee methoden is identiek.

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();
    }
}

Voorbeeld 3

In de volgende voorbeeldbibliotheek ziet u hoe ref parameters voor referentietypen worden gebruikt en ziet u een betere manier om deze functionaliteit te implementeren.

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";
    }
}

Voorbeeld 4

Met de volgende toepassing wordt elke methode in de bibliotheek aangeroepen om het gedrag te demonstreren.

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);
    }
}

In dit voorbeeld wordt de volgende uitvoer gegenereerd:

Changing pointer - passed by value:
12345
12345
Changing pointer - passed by reference:
12345
12345 ABCDE
Passing by return value:
12345 ABCDE

Patroonmethoden proberen

Methoden voor het implementeren van het patroon Try<Something> , zoals System.Int32.TryParse, veroorzaken deze schending niet. In het volgende voorbeeld ziet u een structuur (waardetype) waarmee de System.Int32.TryParse methode wordt geïmplementeerd.

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;
    }
}

CA1045: Geef geen typen door op verwijzing