CA1021: out 매개 변수를 사용하지 마십시오.
TypeName |
AvoidOutParameters |
CheckId |
CA1021 |
범주 |
Microsoft.Design |
변경 수준 |
주요 변경 |
원인
public 형식의 public 또는 protected 메서드에 out 매개 변수가 있습니다.
규칙 설명
out 또는 ref를 사용하여 참조로 형식을 전달하려면 포인터를 사용할 수 있고 값 형식과 참조 형식이 어떻게 다른지 알고 있어야 하며 여러 개의 반환 값이 있는 메서드를 처리할 수 있어야 합니다. 또한 out 매개 변수와 ref 매개 변수의 차이점은 잘 알려져 있지 않습니다.
참조 형식이 "참조로" 전달된 경우에는 메서드가 매개 변수를 사용하여 개체의 다른 인스턴스를 반환하려는 것입니다. 참조 형식을 참조로 전달하는 것을 이중 포인터, 포인터 대 포인터 또는 이중 간접 참조를 사용한다고도 말합니다. 기본 호출 규칙을 사용하면, 즉 "값으로" 전달하면 참조 형식을 사용하는 매개 변수가 이미 개체에 대한 포인터를 받습니다. 이 경우 가리키는 개체가 아닌 포인터가 값으로 전달됩니다. 값으로 전달된다는 것은 메서드에서 포인터를 참조 형식의 인스턴스를 가리키도록 변경할 수 없음을 의미합니다. 그러나 포인터가 가리키는 개체의 내용은 변경할 수 있습니다. 대부분의 응용 프로그램에서는 이 기능으로도 충분하며 원하는 동작을 만들 수 있습니다.
메서드에서 다른 인스턴스를 반환해야 하는 경우에는 메서드의 반환 값을 사용하여 이 작업을 수행합니다. 문자열에 대한 작업을 수행하고 문자열의 새 인스턴스를 반환하는 다양한 메서드의 경우에는 System.String 클래스를 참조하십시오. 이 모델을 사용할 경우 호출자가 원래 개체를 보존할 것인지 여부를 결정해야 합니다.
반환 값은 일반적이며 많이 사용되지만 out 및 ref 매개 변수를 제대로 적용하려면 중급 수준의 디자인 및 코딩 기술이 필요합니다. 일반 사용자를 대상으로 디자인하는 라이브러리 설계자는 사용자가 out 또는 ref 매개 변수를 사용하는 작업에 능숙할 것이라고 기대해서는 안 됩니다.
위반 문제를 해결하는 방법
이 규칙 위반 문제가 값 형식 때문에 발생한 경우 이를 해결하려면 메서드가 반환 값으로 개체를 반환하도록 합니다. 메서드에서 여러 개의 값을 반환하면 여러 값을 갖는 개체의 단일 인스턴스를 반환하도록 다시 디자인합니다.
이 규칙 위반 문제가 참조 형식 때문에 발생한 경우 이를 해결하려면 참조의 새 인스턴스를 반환하는 것이 원하는 동작인지 확인합니다. 그럴 경우 메서드에서 반환 값을 사용하여 이 동작을 수행하도록 합니다.
경고를 표시하지 않는 경우
이 규칙에서는 경고를 표시하지 않아도 안전합니다. 그러나 이 디자인으로 인해 사용 가능성에 문제가 발생할 수 있습니다.
예제
다음 라이브러리에서는 사용자의 피드백에 대한 응답을 생성하는 클래스의 두 가지 구현을 보여 줍니다. 첫 번째 구현(BadRefAndOut)에서는 라이브러리 사용자가 세 가지 반환 값을 관리하도록 합니다. 두 번째 구현(RedesignedRefAndOut)에서는 데이터를 하나의 단위로 관리하는 컨테이너 클래스의 인스턴스(ReplyData)를 반환하여 사용자 작업을 간단하게 합니다.
using System;
namespace DesignLibrary
{
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;
}
}
}
다음 응용 프로그램에서는 사용자 작업을 보여 줍니다. 다시 디자인한 라이브러리를 호출하는 작업(UseTheSimplifiedClass 메서드)은 더욱 간단하고 메서드에서 반환된 정보를 관리하기 쉽습니다. 두 메서드의 출력은 동일합니다.
using System;
namespace DesignLibrary
{
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 Main()
{
UseTheComplicatedClass();
// Print a blank line in output.
Console.WriteLine("");
UseTheSimplifiedClass();
}
}
}
다음 예제 라이브러리에서는 참조 형식에 ref 매개 변수를 사용하는 방법을 보여 주고 이 기능을 구현하는 더 좋은 방법을 보여 줍니다.
using System;
namespace DesignLibrary
{
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";
}
}
}
다음 응용 프로그램에서는 라이브러리의 메서드를 각각 호출하여 해당 동작을 보여 줍니다.
using System;
namespace DesignLibrary
{
public class Test
{
public static void Main()
{
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);
}
}
}
이 예제의 결과는 다음과 같습니다.
Try 패턴 메서드
설명
Int32TryParse()와 같이 Try<Something> 패턴을 구현하는 메서드는 이 위반 문제를 발생시키지 않습니다. 다음 예제에서는 Int32TryParse()를 구현하는 구조체(값 형식)를 보여 줍니다.
코드
using System;
namespace Samples
{
public struct Point
{
private readonly int _X;
private readonly int _Y;
public Point(int axisX, int axisY)
{
_X = axisX;
_Y = axisY;
}
public int X
{
get { return _X; }
}
public int Y
{
get { return _Y; }
}
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;
}
}
}