| 屬性 | 值 |
|---|---|
| 規則識別碼 | CA1045 |
| 職稱 | 不要以參考方式傳遞類型 |
| 類別 | 設計 |
| 修正是會中斷還是不會中斷 | 中斷 |
| 在 .NET 10 中預設啟用 | 不 |
原因
公開類型中的公共或受保護方法具有一個 ref 參數,該參數是基本型別、參考型別,或非內建型別中的實值型別。
規則描述
以傳址方式傳遞類型(使用out或ref)需要具備指標使用經驗、理解實值類型和參考型別的差異,並能處理多個傳回值的方法。 此外,out 和 ref 參數之間的差異一般人不甚了解。
當參考型別以「傳址方式」傳遞時,方法旨在透過參數傳回該物件的不同實例。 (以傳址方式傳遞參考型別也稱為使用雙指標、指到指標的指標或雙重間接。使用預設的呼叫慣例,即傳遞「依值」,使用參考類型的參數已經接收物件的指標。) 指標,而不是其指向的物件,會以傳值方式傳遞。 以傳值方式傳遞意味著方法無法更改指標以使其指向新的參考型別實例,但可以更改該指標所指向對象的內容。 對於大部分的應用程式而言,這已足夠,併產生您想要的行為。
如果方法必須傳回不同的實例,請使用 方法的傳回值來完成這項作業。 如需對字串進行操作並傳回字串新實例的方法,請參閱 System.String 類別。 使用此模型後,是否保留原始物件將由呼叫端決定。
雖然傳回值很常見且被大量使用,但正確應用out和ref參數需要具備中級設計和編程技能。 目標為一般使用者的程式庫架構設計人員不應預期使用者會熟練地運用 out 或 ref 參數。
注意
當您使用大型結構的參數時,當您依值傳遞時,複製這些結構所需的額外資源可能會產生效能影響。 在這些情況下,您可能會考慮使用 ref 或 out 參數。
如何修正違規
若要修正值類型所造成的此規則違規,請讓方法傳回物件作為其傳回值。 如果方法必須傳回多個值,請重新設計它以傳回保存值之物件的單一實例。
若要修正由參考類型所造成的此規則違規,請確定您想要的行為是傳回參考的新執行個體。 如果是,方法應該使用其傳回值來執行此動作。
隱藏警告的時機
隱藏此規則的警告是安全的;不過,此設計可能會導致可用性問題。
隱藏警告
如果您只想要隱藏單一違規,請將預處理器指示詞新增至原始程式檔以停用,然後重新啟用規則。
#pragma warning disable CA1045
// The code that's violating the rule is on this line.
#pragma warning restore CA1045
若要停用檔案、資料夾或項目的規則,請在組態檔
[*.{cs,vb}]
dotnet_diagnostic.CA1045.severity = none
如需詳細資訊,請參閱 如何隱藏程式代碼分析警告。
設定程式代碼以分析
使用下列選項來設定程式代碼基底要執行此規則的部分。
您可以設定此選項,只針對該規則、針對其適用的所有規則,或針對屬於本類別 (設計) 並適用的所有規則。 如需詳細資訊,請參閱 程式代碼品質規則組態選項。
包含特定 API 介面
您可以藉由設定 [api_surface] 選項,根據程式代碼基底的存取範圍,設定執行此規則的哪些部分。 例如,若要指定規則只應該針對非公用 API 介面執行,請將下列機碼/值組新增至 專案中的 .editorconfig 檔案:
dotnet_code_quality.CAXXXX.api_surface = private, internal
注意
以適用規則的標識碼取代 XXXX 的 CAXXXX 部分。
範例 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)
{
string replyText = """
Your feedback has been forwarded to the product manager.
""";
reply = string.Empty;
bool returnReply;
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 record class ReplyData(string Reply, Actions Action, bool ReturnReply = false)
{
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)
{
string replyText = "Your feedback has been forwarded " +
"to the product manager.";
ReplyData? answer = input switch
{
TypeOfFeedback.Complaint or TypeOfFeedback.Praise => new ReplyData(
"Thank you. " + replyText,
Actions.ForwardToManagement,
true),
TypeOfFeedback.Suggestion => new ReplyData(
replyText,
Actions.ForwardToDeveloper,
true),
_ => null,
};
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<TypeOfFeedback>())
{
// The call to the library.
disposition[i] = BadRefAndOut.ReplyInformation(
t, out reply[i], ref action[i]);
Console.WriteLine($"Reply: {reply[i]} Action: {action[i]} return? {disposition[i]} ");
i++;
}
}
static void UseTheSimplifiedClass()
{
ReplyData[] answer = new ReplyData[5];
int i = 0;
foreach (TypeOfFeedback t in Enum.GetValues<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