| 属性 | 值 |
|---|---|
| 规则 ID | CA1045 |
| 标题 | 不要通过引用来传递类型 |
| 类别 | 设计 |
| 修复是中断修复还是非中断修复 | 重大 |
| 在 .NET 9 中默认启用 | 否 |
原因
公共类型中的公共或受保护方法有一个 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
若要对文件、文件夹或项目禁用该规则,请在none中将其严重性设置为 。
[*.{cs,vb}]
dotnet_diagnostic.CA1045.severity = none
有关详细信息,请参阅如何禁止显示代码分析警告。
配置代码以进行分析
使用下面的选项来配置代码库的哪些部分要运行此规则。
可以仅为此规则、为适用的所有规则或为适用的此类别(设计)中的所有规则配置此选项。 有关详细信息,请参阅代码质量规则配置选项。
包含特定的 API 图面
你可以通过设置 api_surface 选项来配置要基于可访问性对代码库的哪些部分运行此规则。 例如,若要指定规则应仅针对非公共 API 图面运行,请将以下键值对添加到项目中的 .editorconfig 文件:
dotnet_code_quality.CAXXXX.api_surface = private, internal
注意
将 XXXX 的 CAXXXX 部分替换为适用规则的 ID。
示例 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 方法)更简单,并且该方法返回的信息非常易于管理。 这两个方法的输出是相同的。
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