CA1021: Vyhněte se výstupním parametrům

Vlastnost Hodnota
ID pravidla CA1021
Název Vyhněte se výstupním parametrům
Kategorie Návrh
Oprava způsobující chybu nebo chybu způsobující chybu Narušující
Povoleno ve výchozím nastavení v .NET 8 No

Příčina

Veřejná nebo chráněná metoda ve veřejném typu má out parametr.

Ve výchozím nastavení toto pravidlo sleduje jenom externě viditelné typy, ale dá se konfigurovat.

Popis pravidla

Předávání typů odkazem (pomocí out nebo ref) vyžaduje zkušenosti s ukazateli, pochopení, jak se typy hodnot a odkazové typy liší, a zpracování metod s více návratovými hodnotami. Také rozdíl mezi out parametry ref a parametry není široce srozumitelný.

Pokud je typ odkazu předán "podle odkazu", metoda hodlá použít parametr k vrácení jiné instance objektu. Předání typu odkazu odkazem podle odkazu se označuje také jako použití dvojitého ukazatele, ukazatele na ukazatel nebo dvojitého nepřímých odkazů. Pomocí výchozí konvence volání, která se předává "podle hodnoty", parametr, který přebírá typ odkazu, již přijímá ukazatel na objekt. Ukazatel, nikoli objekt, na který odkazuje, je předán hodnotou. Předání hodnoty znamená, že metoda nemůže změnit ukazatel tak, aby odkazovat na novou instanci typu odkazu. Může ale změnit obsah objektu, na který odkazuje. U většiny aplikací je to dostatečné a přináší požadované chování.

Pokud metoda musí vrátit jinou instanci, použijte k tomu návratovou hodnotu metody. System.String Prohlédněte si třídu pro různé metody, které pracují s řetězci a vracejí novou instanci řetězce. Při použití tohoto modelu musí volající rozhodnout, zda je původní objekt zachován.

I když jsou návratové hodnoty běžné a silně používané, správná aplikace out a ref parametry vyžaduje středně pokročilé dovednosti v oblasti návrhu a kódování. Architekti knihoven, kteří navrhují pro širokou cílovou skupinu, by neměli očekávat, že uživatelé budou moct pracovat s parametry nebo ref s out parametry.

Jak opravit porušení

Chcete-li opravit porušení tohoto pravidla, které je způsobeno typem hodnoty, vraťte metodu objekt jako jeho návratovou hodnotu. Pokud metoda musí vrátit více hodnot, přepracujte ji tak, aby vrátila jednu instanci objektu, který obsahuje hodnoty.

Chcete-li opravit porušení tohoto pravidla, které je způsobeno typem odkazu, ujistěte se, že požadované chování je vrátit novou instanci odkazu. Pokud ano, metoda by k tomu měla použít jeho návratovou hodnotu.

Kdy potlačit upozornění

Je bezpečné potlačit upozornění z tohoto pravidla. Tento návrh ale může způsobit problémy s použitelností.

Potlačení upozornění

Pokud chcete pouze potlačit jedno porušení, přidejte do zdrojového souboru direktivy preprocesoru, abyste pravidlo zakázali a znovu povolili.

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

Pokud chcete pravidlo pro soubor, složku nebo projekt zakázat, nastavte jeho závažnost v none konfiguračním souboru.

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

Další informace naleznete v tématu Jak potlačit upozornění analýzy kódu.

Konfigurace kódu pro analýzu

Pomocí následující možnosti nakonfigurujte, ve kterých částech základu kódu se má toto pravidlo spouštět.

Tuto možnost můžete nakonfigurovat jenom pro toto pravidlo, pro všechna pravidla, která platí, nebo pro všechna pravidla v této kategorii (Návrh), na která platí. Další informace naleznete v tématu Možnosti konfigurace pravidla kvality kódu.

Zahrnutí konkrétních povrchů rozhraní API

Na základě přístupnosti můžete nakonfigurovat, na kterých částech základu kódu se má toto pravidlo spouštět. Pokud chcete například určit, že pravidlo by se mělo spouštět jenom na neveřejné ploše rozhraní API, přidejte do souboru .editorconfig v projektu následující pár klíč-hodnota:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Příklad 1

Následující knihovna ukazuje dvě implementace třídy, která generuje odpovědi na zpětnou vazbu uživatelů. První implementace (BadRefAndOut) vynutí, aby uživatel knihovny spravil tři návratové hodnoty. Druhá implementace (RedesignedRefAndOut) zjednodušuje uživatelské prostředí vrácením instance třídy kontejneru (ReplyData), která spravuje data jako jednu jednotku.

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

Příklad 2

Následující aplikace znázorňuje prostředí uživatele. Volání přepracované knihovny (UseTheSimplifiedClass metody) je jednodušší a informace vrácené metodou jsou snadno spravované. Výstup z těchto dvou metod je identický.

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

Příklad 3

Následující ukázková knihovna ukazuje, jak ref se používají parametry pro odkazové typy a ukazuje lepší způsob implementace této funkce.

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

Příklad 4

Následující aplikace volá každou metodu v knihovně k předvedení chování.

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

Tento příklad vytvoří následující výstup:

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

Vyzkoušení metod vzoru

Metody, které implementují vzor Try<Something> , například System.Int32.TryParse, nevyvolají toto porušení. Následující příklad ukazuje strukturu (typ hodnoty), která implementuje metodu 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;
    }
}

CA1045: Nepředávejte typy odkazem