Share via


CA1045: 참조로 참조 형식을 전달하지 않습니다.

속성
규칙 ID CA1045
타이틀 참조로 형식을 전달하지 마세요.
범주 디자인
수정 사항이 주요 변경인지 여부 주요 변경
.NET 8에서 기본적으로 사용 아니요

원인

public 형식의 public 메서드 또는 protected 메서드에 기본 형식, 참조 형식 또는 기본 제공 형식 중 하나가 아닌 값 형식을 사용하는 ref 매개 변수가 있습니다.

규칙 설명

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

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

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

반환 값은 일반적이고 많이 사용되지만 outref 매개 변수를 올바르게 적용하기 위해서는 중급 디자인 및 코딩 기술이 필요합니다. 일반 사용자를 대상으로 디자인하는 라이브러리 설계자는 사용자가 out 또는 ref 매개 변수를 사용하는 작업에 능숙해질 것으로 기대해서는 안 됩니다.

참고 항목

구조가 큰 매개 변수를 사용하는 경우 해당 구조체를 복사하는 데 필요한 추가 리소스를 사용하면 값으로 전달할 때 성능에 영향을 미칠 수 있습니다. 해당 경우 ref 또는 out 매개 변수 사용을 고려할 수 있습니다.

위반 문제를 해결하는 방법

값 형식으로 인해 발생하는 규칙 위반 문제를 해결하려면 메서드가 개체를 반환 값으로 반환하게 합니다. 메서드가 여러 값을 반환해야 할 때는 값을 포함하는 개체의 단일 인스턴스를 반환하도록 다시 디자인합니다.

참조 형식으로 인해 발생하는 규칙 위반 문제를 해결하려면 원하는 동작이 참조의 새 인스턴스를 반환하는지 확인합니다. 반환하는 경우 메서드는 반환 값을 사용하여 해당 작업을 수행해야 합니다.

경고를 표시하지 않는 경우

해당 규칙에서 경고를 표시하지 않아도 안전합니다. 그러나 이 디자인으로 인해 유용성 문제가 발생할 수 있습니다.

경고 표시 안 함

단일 위반만 표시하지 않으려면 원본 파일에 전처리기 지시문을 추가하여 규칙을 사용하지 않도록 설정한 후 다시 사용하도록 설정합니다.

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

파일, 폴더 또는 프로젝트에 대한 규칙을 사용하지 않도록 설정하려면 구성 파일에서 심각도를 none으로 설정합니다.

[*.{cs,vb}]
dotnet_diagnostic.CA1045.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
{
    string reply;
    Actions action;
    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 { return reply; } }
    public Actions Action { get { return action; } }

    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 Main1045()
    {
        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 = argument + " ABCDE";
    }

    // The following syntax will work, 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 = argument + " ABCDE";
    }

    // The following syntax will work 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 Main1045()
    {
        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

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