CA1021: out 매개 변수를 사용하지 마십시오.

속성
규칙 ID CA1021
타이틀 out 매개 변수를 사용하지 마세요.
범주 디자인
수정 사항이 주요 변경인지 여부 주요 변경
.NET 8에서 기본적으로 사용 아니요

원인

public 형식의 public 또는 protected 메서드에 out 매개 변수가 있습니다.

기본적으로 이 규칙은 외부에 표시되는 형식만 확인하지만 이는 구성 가능합니다.

규칙 설명

out 또는 ref를 사용하여 참조로 형식을 전달하려면 포인터 사용 방법을 알고 있어야 하고 값 형식과 참조 형식이 어떻게 다른지 알고 있어야 하며 반환 값이 여러 개인 메서드를 처리할 수 있어야 합니다. 또한 out 매개 변수와 ref 매개 변수의 차이점은 잘 알려져 있지 않습니다.

참조 형식이 “참조로” 전달되는 경우 메서드는 매개 변수를 사용하여 개체의 다른 인스턴스를 반환합니다. 참조 형식을 참조로 전달하는 것은 이중 포인터, 포인터에 대한 포인터 또는 이중 간접 참조를 사용하는 것으로 알려져 있습니다. “값으로” 전달되는 기본 호출 규칙을 사용하여 참조 형식을 사용하는 매개 변수는 이미 개체에 대한 포인터를 수신합니다. 포인터가 가리키는 개체가 아닌 포인터가 값으로 전달됩니다. 값으로 전달은 메서드가 참조 형식의 새 인스턴스를 가리키도록 포인터를 변경할 수 없음을 의미합니다. 그러나 가리키는 개체의 콘텐츠는 변경할 수 있습니다. 대부분의 애플리케이션에 이 방법은 충분하며 원하는 동작을 생성합니다.

메서드가 다른 인스턴스를 반환해야 하는 경우 메서드의 반환 값을 사용하여 이를 수행합니다. 문자열에 대해 작동하고 문자열의 새 인스턴스를 반환하는 다양한 메서드는 System.String 클래스를 참조하세요. 이 모델을 사용하는 경우 호출자는 원래 개체가 유지되는지를 결정해야 합니다.

반환 값은 일반적이고 많이 사용되지만 outref 매개 변수를 올바르게 적용하기 위해서는 중급 디자인 및 코딩 기술이 필요합니다. 일반 사용자를 대상으로 디자인하는 라이브러리 설계자는 사용자가 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 표면에서만 실행되도록 지정하려면 프로젝트의 .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 메서드)를 호출하는 것은 더 간단하며 메서드에서 반환되는 정보는 쉽게 관리할 수 있습니다. 두 메서드에서 출력은 동일합니다.

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 패턴 메서드

Try Something 패턴(예: System.Int32.TryParseTry<Something>)을 구현하는 메서드는 이 위반을 발생시키지 않습니다. 다음 예제에서는 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: 참조로 참조 형식을 전달하지 않습니다.