CA1045: 型を参照によって渡しません
TypeName |
DoNotPassTypesByReference |
CheckId |
CA1045 |
分類 |
Microsoft.Design |
互換性に影響する変更点 |
あり |
原因
パブリック型のパブリック メソッドまたはプロテクト メソッドに、プリミティブ型、参照型、または組み込み型ではない値型を使用する ref パラメーターがあります。
規則の説明
(out または ref を使用した) 型の参照渡しには、ポインターの使用経験、値の型と参照型の違いの理解、および複数の戻り値を持つメソッドの処理が必要です。また、out パラメーターと ref パラメーターの違いがあまり理解されていません。
参照型の "参照" 渡しの場合、メソッドは、オブジェクトの別のインスタンスを返すパラメーターを使用しようとします。参照型の参照渡しは、ダブル ポインターの使用、ポインターへのポインター、または二重の間接参照とも呼ばれます。既定の呼び出し規約では、"値" 渡しですが、参照型を使用するパラメーターは、既にオブジェクトに対するポインターを受け取っています。ポインター (指し示す先のオブジェクトではなく) は、値渡しです。値渡しとは、メソッドで、ポインターの指し示す先を新しいインスタンスの参照型に変更できなくても、指し示す先のオブジェクトのコンテンツは変更できるということです。ほとんどのアプリケーションは値渡しで十分で、目的の動作を実現できます。
メソッドで異なるインスタンスを返す必要がある場合、メソッドの戻り値を使用して実現します。文字列を操作するさまざまなメソッドの String クラスを参照して、文字列の新しいインスタンスを返します。呼び出し元は、このモデルを使用して元のオブジェクトが保存されているかどうかを判断します。
戻り値は一般的であり、よく使用されますが、out パラメーターと ref パラメーターを適切に使用する場合、中間のデザインとコーディング技術が必要です。開発者全般に向けてライブラリをデザインする場合、ユーザーが out パラメーターまたは ref パラメーターの扱い方を習得することは期待しないでください。
[!メモ]
大きな構造体であるパラメーターを扱う場合、そのような構造体をコピーするために追加のリソースが必要になるため、値を渡すときにパフォーマンスの低下を招くことがあります。この場合、ref パラメーターまたは out パラメーターを使用することをお勧めします。
違反の修正方法
値型によって発生したこの規則違反を修正するには、メソッドからオブジェクトを戻り値として返すようにします。メソッドで複数の値を返す必要がある場合、値を保持しているオブジェクトの 1 インスタンスを返すようにメソッドをデザインし直します。
参照型によって発生したこの規則違反を修正するには、目的の動作が参照の新しいインスタンスを返すことであることを確認します。この場合、メソッドで戻り値を使用して実行します。
警告を抑制する状況
この規則による警告を抑制しても安全ですが、このデザインのままでは操作性の問題が発生することがあります。
使用例
次のライブラリで、ユーザーのフィードバックに対して応答を生成するクラスの実装例を 2 つ示します。最初の実装 (BadRefAndOut) によって、ライブラリ ユーザーは 3 つの戻り値を管理することになります。2 つ目の実装 (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 メソッド) はわかりやすく、このメソッドで返される情報は管理が容易です。2 つのメソッドの出力結果は同じです。
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);
}
}
}
この例を実行すると、次の出力が生成されます。