CA2227或更好方法的解决方案?

时间:2015-05-20 20:40:23

标签: c# visual-studio code-analysis

我只使用代码分析进行清理,组织并确保针对特定警告的所有实例全局执行这些更改。我进入决赛,这是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; }

}

您可以看到所有Collection属性都会给我这个警告,并且有数百个。使用上面的类时,我实例化它没有任何数据。然后在外部我添加数据并通过其公共访问器设置每个单独的变量。我没有使用构造函数方法准备和传递所有数据来实例化此类(IMO的大小可以达到它可以轻易地对眼睛造成严重破坏)。完成并分配所有属性后,整个类将用于生成它所代表的文档的那一部分。

我的问题是,对于上述用法,有什么更好的方法来正确设置它?我是否保留公共访问者并完全抑制此警告,或者是否存在完全不同的解决方案?

8 个答案:

答案 0 :(得分:17)

以下是MSDN对错误的说法,以及如何避免错误。

这是我对这个问题的看法。

考虑以下课程:

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

这个类将抛出完全相同的问题。为什么?因为Collections 需要设置者。现在,我们可以对该对象执行任何:将Data分配给任意List<string>,将元素添加到Data,从Data中删除元素,等。如果我们移除setter,我们就会失去直接分配给该属性的功能。

请考虑以下代码:

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>是内存位置的引用,它包含剩余的数据。

现在,您现在无法使用 的事情,直接设置 Data属性。即以下内容无效:

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

不一定是坏事。您会注意到在许多 .NET类型上使用了此模型。它是不变性的基础。您通常不希望直接访问Collections的可变性,因为这会导致一些有奇怪问题的意外行为。这就是Microsoft建议您省略setter的原因。

示例:

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,因此无法可靠地确定是添加了新值还是删除了值。

  

可写集合属性允许用户使用完全不同的集合替换集合。

基本上,如果你只需要运行构造函数或赋值,那么省略set修饰符。你不会需要它,直接分配集合是违反最佳实践的。

现在,我并不是说{em>永远不会在Collection上使用setter ,有时你可能需要一个,但一般来说你永远不应该使用它们。 (当我回到家时,我会将serialization个例子发布到收藏中。)

您可以始终在.AddRange上使用.CloneCollections等,会失去direct assignment的能力。

序列化

最后,如果我们希望SerializeDeserialize包含我们的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(这通常是一个更好的选择。)您可以在this StackOverflow question上找到有关它的信息。

答案 1 :(得分:1)

感谢@Matthew,@ ClaigW和@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或任何其他方法来修改集合。

答案 2 :(得分:1)

DTO 通常需要序列化和反序列化。因此,它们必须是可变的。

必须创建备用支持属性是一种痛苦。
只需将属性类型从 List<string> 更改为 IReadOnlyList<string>,然后无需 CA2227 即可正常工作。

集合是通过属性设置的,但如果您希望附加或删除项目,您也可以将其转换为 List<string>

class Holder
{
    public IReadOnlyList<string> Col { get; set; } = new List<string>();
}

var list = new List<string> { "One", "Two" };
var holder = new Holder() { Col = list } ;
var json = JsonConvert.SerializeObject(holder);
// output json {"Col":["One","Two"]}
var deserializedHolder = JsonConvert.DeserializeObject<Holder>(json);

答案 3 :(得分:0)

我必须修复一些CA2227违规行为,所以我不得不添加&#34; readonly&#34;关键字到集合字段然后,当然,不得不删除setter属性。一些使用了setter的代码,只是创建了一个最初为空的新集合对象。这段代码肯定没有编译,所以我不得不添加一个SetXxx()方法,以实现缺少的setter的功能。我是这样做的:

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

使用setter的调用者代码已被替换为方法SetXxx()的调用。

现在的列表将被清除并填充来自另一个列表的新项目,而不是创建一个完整的新列表,作为参数传入。原始列表由于它是只读的并且只创建一次,因此将始终保留。

我相信这也是避免garbagae收集器必须删除超出范围的旧对象的好方法,其次是创建新的收集对象,尽管已经存在。

答案 4 :(得分:0)

在当前的VS2019中,我们可以简单地做到这一点:

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

这满足CA2227,可以序列化/反序列化。

反序列化之所以有效,是因为List <>具有一个“ Add”方法,并且序列化程序知道如何使用Add方法处理只读collection属性(该属性是只读的,但不是元素)(我使用Json .Net,其他序列化器的行为可能会有所不同。

答案 5 :(得分:0)

在我的情况下,将属性设置为只读是不可行的,因为整个列表(作为参考的 )可以更改为新列表。

我可以通过将范围更改为internal来解决此警告,或者可能private也可以工作:

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

此警告的提示( achilleas治愈)似乎确实指向文档说明为(Bolding mine)的库:

  

外部可见的可写属性是一种可实现的类型   System.Collections.ICollection。

对我来说,是的,“好吧,我不会使其在外部可见。...”并且internal对于该应用程序来说很好。

答案 6 :(得分:0)

涵盖解决CA2227错误的所有可能情况: 这涵盖了当我们使用实体框架时的实体关系映射。

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>();
}

答案 7 :(得分:0)

仅在绑定DTO时,才需要禁止显示警告。 否则,需要自定义ModelBinder来绑定集合。

引用规则文档:

何时禁止显示警告

如果该属性是数据传输对象(DTO)类的一部分,则可以禁止显示警告。
否则,请勿根据此规则禁止警告。

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