CA1021:避免使用 out 參數

財產 價值
規則識別碼 CA1021
職稱 避免使用 out 參數
類別 設計
此更新是破壞性還是非破壞性 中斷
在 .NET 10 中預設啟用 不
適用語言 C# 與 Visual Basic

原因

公用類型中的公用或受保護方法具有 out 參數。

根據預設,此規則只會查看外部可見的類型,但這是可設定的。

規則描述

以傳址方式傳遞型別(使用 out 或 ref)需要指標的經驗、了解實值型別和參考型別的差異,以及處理具有多個傳回值的方法。 此外,out 和 ref 參數之間的差異一般人不甚了解。

當參考型別透過參考傳遞時,方法會使用參數以傳回一個不同的物件實例。 以傳址方式傳遞參考型別也稱為使用雙重指標、指向指標的指標或雙重間接。 藉由使用預設的呼叫慣例,參數是「按值」傳遞的,但採用參考類型的參數已經接收到了物件的指標。 指標,而不是其指向的物件,會以傳值方式傳遞。 傳遞值表示方法無法變更指標,使其指向參考型別的新實例。 不過,它可以變更其所指向的對象內容。 對於大部分的應用程式而言,這已足夠,併產生所需的行為。

如果方法必須傳回不同的實例,請使用 方法的傳回值來完成這項作業。 請參閱 System.String 類別以獲取各種在字串上操作的方法,這些方法將傳回字串的新實例。 使用此模型時,呼叫端必須決定是否保留原始物件。

雖然傳回值很常見且大量使用,但正確應用傳回值和out 參數ref需要中級的設計和程式代碼撰寫技能。 目標為一般使用者的程式庫架構設計人員不應預期使用者會熟練地運用 out 或 ref 參數。

如何修正違規

若要修正因實值類型違反此規則的問題,請讓方法將物件作為其傳回值返回。 如果方法必須傳回多個值,請重新設計它以傳回保存值之物件的單一實例。

若要修正因引用類型所造成此規則的違規,請確定所需的行為是傳回參考的新實例。 如果是,方法應該使用其傳回值來執行此動作。

隱藏警告的時機

隱藏此規則的警告是安全的。 不過,此設計可能會導致可用性問題。

隱藏警告

如果您只想要隱藏單一違規,請將預處理器指示詞新增至原始程式檔以停用,然後重新啟用規則。

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

若要停用檔案、資料夾或項目的規則,請在組態檔案中將其嚴重性設為none。

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

如需詳細資訊,請參閱 如何隱藏程式代碼分析警告。

設定程式代碼以分析

使用下列選項來設定程式代碼基底要執行此規則的部分。

您可以只針對此規則,針對它套用的所有規則,或針對它套用的此類別(設計)中的所有規則,設定此選項。 如需詳細資訊,請參閱 程式代碼品質規則組態選項。

包含特定 API 介面

您可以藉由設定 [api_surface] 選項,根據程式代碼基底的存取範圍,設定執行此規則的哪些部分。 例如,若要指定規則只應該針對非公用 API 介面執行,請將下列機碼/值組新增至 專案中的 .editorconfig 檔案:

dotnet_code_quality.CAXXXX.api_surface = private, internal

注意

以適用規則的標識碼取代 XXXX 的 CAXXXX 部分。

範例 1

下列連結庫顯示類別的兩個實作,這些實作會產生對用戶意見反應的回應。 第一個實作 (BadRefAndOut) 會強制連結庫使用者管理三個傳回值。 第二個實作 (RedesignedRefAndOut) 會傳回容器類別的實例,ReplyData以單一單位方式管理數據,藉此簡化用戶體驗。

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

範例 2

下列應用程式說明用戶的體驗。 對重新設計的連結庫 (UseTheSimplifiedClass method) 的呼叫比較簡單,而且方法傳回的資訊很容易管理。 兩個方法的輸出完全相同。

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: {reply[i]} Action: {action[i]}  return? {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();
    }
}

範例 3

下列範例連結庫說明如何使用 ref 參考類型的參數,並示範實作此功能的更好方式。

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

範例 4

下列應用程式會呼叫連結庫中的每個方法,以示範行為。

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

這個範例會產生下列輸出:

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

嘗試模式方法

實作 Try<Something> 模式的方法,例如 System.Int32.TryParse,不會造成此違規。 下列範例顯示實作 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: 不要以參考方式傳遞類型