web-dev-qa-db-ja.com

CA2227以上のアプローチのソリューション?

私は、特定の警告のすべてのインスタンスに対してこれらの変更がグローバルに実行されるように、クリーニング、整理、および確認するためにのみコード分析を使用しています。決勝まで進んでいます。CA2227です。

CA2227コレクションプロパティは読み取り専用である必要がありますプロパティセッターを削除して、 ''を読み取り専用に変更します。

これはEDIドキュメントのマッピング用であることに注意してください。これらのクラスは、EDIドキュメント。の全体または一部を表すためのものです。

public class PO1Loop
{

    public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }

    public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; set; }

    public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; set; }

    public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }

    /* Max Use: 8 */
    public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; set; }

}

すべてのコレクションプロパティでこの警告が表示され、何百ものプロパティがあることがわかります。上記のクラスを使用する場合、データなしでインスタンス化します。次に、外部でデータを追加し、パブリックアクセサーを介して個々の変数を設定します。コンストラクターメソッドを使用して準備および渡されたすべてのデータを使用してこのクラスをインスタンス化するわけではありません(これらが到達できるサイズのIMOは、目に大混乱をもたらす可能性があります)。完了し、すべてのプロパティが割り当てられると、クラス全体が、それが表すドキュメントのその部分を生成するために使用されます。

私の質問は、上記の使用法について、これを正しく設定するためのより良いアプローチは何でしょうか?パブリックアクセサーを維持し、この警告を完全に抑制しますか、それともまったく異なる解決策が機能しますか?

19
David Carrigan

これが [〜#〜] msdn [〜#〜] がエラーについて言っていることと、それを回避する方法です。

これがこの問題に関する私の見解です。

次のクラスについて考えてみます。

class BigDataClass
{
    public List<string> Data { get; set; }
}

このクラスは、まったく同じ問題をスローします。どうして? Collections donotにはセッターが必要ないためです。これで、そのオブジェクトを使用してanythingを実行できます。Dataを任意のList<string>に割り当て、DataDataなどから要素を削除します。setterを削除すると、onlyは能力を失いますtoそのプロパティに直接割り当てます。

次のコードについて考えてみます。

class BigDataClass
{
    private List<string> data = new List<string>();
    public List<string> Data { get { return data; } } // note, we removed the setter
}

var bigData = new BigDataClass();
bigData.Data.Add("Some String");

このコードは完全に有効であり、実際には推奨される方法です 。どうして? List<string>はメモリ位置への参照であるため、残りのデータが含まれます。

さて、これで今はできないonlyは、直接設定Dataプロパティです。つまり以下は無効です。

var bigData = new BigDataClass();
bigData.Data = new List<string>();

これは必ずしも悪いことではありません。 多くの。NETタイプでは、このモデルが使用されていることに気付くでしょう。それが不変性の基本です。あなたは通常Collectionsの可変性に直接アクセスすることを望んでいません。これは、奇妙な問題を伴う偶発的な動作を引き起こす可能性があるためです。これが、Microsoftがセッターを省略することを推奨する理由です。

例:

var bigData = new BigDataClass();
bigData.Data.Add("Some String");
var l2 = new List<string>();
l2.Add("String 1");
l2.Add("String 2");
bigData.Data = l2;
Console.WriteLine(bigData.Data[0]);

Some Stringを期待しているかもしれませんが、String 1を取得します。これは、問題のCollectionにイベントを確実に添付できないため、新しい値が追加されたかどうかを確実に判断できないことも意味します。値が削除されます。

書き込み可能なコレクションプロパティを使用すると、ユーザーはコレクションを完全に異なるコレクションに置き換えることができます。

基本的に、コンストラクターまたは割り当てを1回だけever実行する必要がある場合は、set修飾子を省略します。コレクションを直接割り当てることは、ベストプラクティスに反します。

さて、私はCollectionでセッターを使用しないと言っているわけではありません。必要な場合もありますが、一般的には使用しないでください。 (家に帰ったら、コレクション付きのserializationの例を投稿します。)

Collectionsではいつでも.AddRange.Cloneなどを使用できます。あなたのみは能力を失いますdirect assignmentの。

シリアル化

最後に、SerializeなしでDeserializeを含むクラスをCollectionまたはsetしたい場合はどうすればよいですか?そうですね、それを行うには常に複数の方法があります。(私の意見では)最も簡単なのは、シリアル化されたコレクションを表すpropertyを作成することです。

BigDataClassを例にとってみましょう。次のコードを使用してこのクラスをSerialize、次にDeserializeしたい場合、Dataプロパティには要素がありません。

JavaScriptSerializer jss = new JavaScriptSerializer();
BigDataClass bdc = new BigDataClass();
bdc.Data.Add("Test String");
string serd = jss.Serialize(bdc);
Console.WriteLine(serd);
BigDataClass bdc2 = jss.Deserialize<BigDataClass>(serd);

したがって、これを修正するには、BigDataClassを少し変更して、stringの目的で新しいSerializationプロパティを使用するようにします。

public class BigDataClass
{
    private List<string> data = new List<string>();
    [ScriptIgnore]
    public List<string> Data { get { return data; } } // note, we removed the setter

    public string SerializedData { get { JavaScriptSerializer jss = new JavaScriptSerializer(); return jss.Serialize(data); } set { JavaScriptSerializer jss = new JavaScriptSerializer(); data = jss.Deserialize<List<string>>(value); } }
}

別のオプションは常にDataContractSerializerです(これは一般的に本当に良いオプションです) このStackOverflowの質問 でそれに関する情報を見つけることができます。

25
Der Kommissar

現在のVS2019では、これを簡単に行うことができます。

public List<string> Data { get; } = new List<string>();

これはCA2227を満たし、シリアル化/逆シリアル化できます。

List <>には「Add」メソッドがあり、シリアライザーはAddメソッドを使用して読み取り専用のコレクションプロパティを処理する方法を知っているため、逆シリアル化は機能します(プロパティは読み取り専用ですが要素ではありません)(私はJson.Netを使用しています。他のシリアライザーは異なる動作をする可能性があります)。

編集:指摘されているように、「=>」ではなく「=」である必要があります(コンパイラは「=>」の使用を禁止します)。 「publicListData => newList();」を使用した場合そうすると、プロパティにアクセスするたびに新しいリストが作成されますが、これも必要なものではありません。

6
Etherman

???? 代替ソリューション ????

私の状況では、リスト全体(を参照として)が新しいリストに変更される可能性があるため、プロパティを読み取り専用にすることは実行可能ではありませんでした。

プロパティのセッタースコープをinternalに変更することで、この警告を解決することができました。

public List<Batch> Batches
{
    get { return _Batches; }
    internal set { _Batches = value; OnPropertyChanged(nameof(Batches)); }
}

private set...も使用できることに注意してください


この警告のヒント(achilleas heal)は、ドキュメントのライブラリを実際に指し示しているようです(太字の鉱山):

外部から見える書き込み可能なプロパティは、System.Collections.ICollectionを実装するタイプです。

私にとっては、「わかりました。外部から表示することはしません。..」で、アプリにはinternalで問題ありませんでした。

2
ΩmegaMan

この警告の解決策を理解するのを手伝ってくれた@ Matthew、@ CraigW、@ EBrownに感謝します。

public class PO1Loop
{

    public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }

    public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; private set; }

    public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; private set; }

    public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }

    /* Max Use: 8 */
    public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; private set; }

    public PO1Loop()
    {
        PIDRepeat1 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID1>();
        PIDRepeat2 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID2>();
        ACKRepeat = new Collection<SegmentTypes.PO1LoopSegmentTypes.ACK>();
    }

}

コレクションタイプにデータを割り当てたい場合は、AddRange、Clear、またはその他のバリエーションのメソッドを使用してコレクションを変更します。

1
David Carrigan

CA2227違反の一部を修正する必要があったため、コレクションフィールドに「readonly」キーワードを追加してから、もちろん、setterプロパティを削除する必要がありました。セッターを使用した一部のコードは、最初は空だった新しいコレクションオブジェクトを作成しました。このコードは確かにコンパイルされなかったので、不足しているセッターの機能を実現するためにSetXxx()メソッドを追加する必要がありました。私はこのようにしました:

public void SetXxx(List<string> list)
{
    this.theList.Clear();
    this.theList.AddRange(list);
}

セッターを使用する呼び出し元のコードは、メソッドSetXxx()の呼び出しに置き換えられました。

完全に新しいリストを作成する代わりに、既存のリストがクリアされ、別のリストからの新しいアイテムが入力され、パラメーターとして渡されます。元のリストは読み取り専用であり、一度だけ作成されるため、常に残ります。

これは、garbagaeコレクターがスコープ外になった古いオブジェクトを削除し、新しいコレクションオブジェクトを作成する必要があることを回避するための良い方法でもあると思います。

0
brighty

CA2227エラーを解決するために考えられるすべてのシナリオをカバーするため:これは、EntityFrameworkを使用する場合のエンティティ関係マッピングをカバーします。

class Program
{

    static void Main(string[] args)
    {
        ParentClass obj = new ParentClass();

        obj.ChildDetails.Clear();
        obj.ChildDetails.AddRange();

        obj.LstNames.Clear();
        obj.LstNames.AddRange();


    }


}
public class ChildClass
{ }
public class ParentClass
{
    private readonly ICollection<ChildClass> _ChildClass;
    public ParentClass()
    {
        _ChildClass = new HashSet<ChildClass>();
    }

    public virtual ICollection<ChildClass> ChildDetails => _ChildClass;
    public IList<string> LstNames => new List<string>();
}

DTOをバインドしている間のみ、警告を抑制する必要があります。それ以外の場合は、コレクションをバインドするためにカスタムModelBinderが必要です。

ルールのドキュメントを引用する:

警告を抑制するタイミング

プロパティがデータ転送オブジェクト(DTO)クラスの一部である場合は、警告を抑制できます。
それ以外の場合は、このルールからの警告を抑制しないでください。

https://docs.Microsoft.com/pt-br/visualstudio/code-quality/ca2227?view=vs-2019

0
Khalil