CA1021:避免使用 out 參數
型別名稱 |
AvoidOutParameters |
CheckId |
CA1021 |
分類 |
Microsoft.Design |
中斷變更 |
中斷 |
原因
公用型別中之公用或保護的方法具有 out 參數。
規則描述
以傳址方式傳遞型別時 (使用 out 或 ref),您需要擁有使用指標的經驗、了解實值型別和參考型別之間的差異,並處理具有多個傳回值的方法。不過 out 和 ref 參數之間的差異一般人不甚了解。
以「傳址方式」傳遞參考型別時,方法會使用參數傳回不同的物件執行個體 以傳址方式傳遞參考型別也就是使用雙重指標、指標對指標或雙重間接取值 (Indirection)。藉由使用以「傳值方式」傳遞的預設呼叫慣例 (Calling Convention),採用參考型別的參數即已經接收到物件的指標。以傳值方式傳遞指標,而不是傳遞所指向的物件。以傳值方式傳遞代表方法不能變更指標,讓指標指向參考型別的新執行個體。但是,它可以變更所指向物件的內容。對大多數應用程式來說,這樣就已足夠並可產生想要的行為。
如果方法必須傳回不同的執行個體,請使用方法的傳回值以完成這項作業。請參閱 String 類別,以取得在字串上作業並傳回字串新執行個體的多種方法。使用這個模型時,呼叫端必須決定是否要保留原始物件。
雖然傳回值很常見並且大量使用,但還是需要中等的設計和編碼技能,才能正確地應用 out 和 ref 參數。針對一般使用者所設計的程式庫架構不應預期使用者會熟練地運用 out 或 ref 參數。
如何修正違規
若要修正因實值型別而引發此規則的違規情形,可讓方法傳回物件當做它的傳回值。如果方法必須傳回多個值,請將方法重新設計,以傳回持有值之物件的單一執行個體。
若要修正因參考型別而引發此規則的違規情形,請確定所需的行為就是傳回參考的新執行個體。如果是,則方法應使用它的傳回值以完成這項作業。
隱藏警告的時機
您可以放心地隱藏這項規則的警告。然而,這種設計可能會造成可用性問題。
範例
下列程式庫會示範類別的兩種實作,而這個類別會產生使用者意見的回應。第一個實作 (BadRefAndOut) 會強制程式庫使用者管理三個傳回值。而第二個實作 (RedesignedRefAndOut) 會透過傳回容器 (Container) 類別 (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 模式方法
說明
實作 Try<Something> 模式的方法 (例如 Int32.TryParse) 不會引發這個違規。下列範例顯示實作 Int32.TryParse 方法的結構 (實值型別,Value Type)。
程式碼
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;
}
}
}