CA1021:避免使用 out 參數

屬性
規則識別碼 CA1021
標題 避免使用 out 參數
類別 設計
修正程式是中斷或非中斷 中斷
預設在 .NET 8 中啟用 No

原因

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

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

檔案描述

以傳址方式傳遞型別(使用 outref)需要指標的經驗、了解實值型別和參考型別的差異,以及處理具有多個傳回值的方法。 此外,與參數之間的差異outref並不廣為人知。

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

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

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

如何修正違規

若要修正因實值類型所造成此規則的違規,方法會傳回 物件做為其傳回值。 如果方法必須傳回多個值,請重新設計它以傳回保存值之物件的單一實例。

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

隱藏警告的時機

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

隱藏警告

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

#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 介面執行,請將下列機碼/值組新增至 專案中的 .editorconfig 檔案:

dotnet_code_quality.CAXXXX.api_surface = private, internal

範例 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: {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();
    }
}

範例 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:不要以傳址方式傳遞類型