CA2227的解决方案还是更好的方法?

Dav*_*gan 13 c# code-analysis visual-studio

我只使用代码分析进行清理,组织并确保针对特定警告的所有实例全局执行这些更改.我到了决赛,这是CA2227.

CA2227集合属性应该是只读的通过删除属性setter将''变为只读.

请注意,这是用于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; }

}
Run Code Online (Sandbox Code Playgroud)

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

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

410*_*one 20

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

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

考虑一下,下面的课程:

class BigDataClass
{
    public List<string> Data { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这个类将抛出完全相同的问题.为什么?因为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");
Run Code Online (Sandbox Code Playgroud)

这段代码完全有效,实际上是推荐的做事方式.为什么?因为它List<string>是对内存位置的引用,它包含剩余的数据.

现在,只有你现在不能用这个做的一件事,是直接设置Data属性.即以下内容无效:

var bigData = new BigDataClass();
bigData.Data = new List<string>();
Run Code Online (Sandbox Code Playgroud)

不一定是坏事.您会注意到在许多 .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]);
Run Code Online (Sandbox Code Playgroud)

我们可能会期待Some String,但我们会得到String 1.这也意味着您无法可靠地将事件附加到相关Collection问题,因此您无法可靠地确定是添加了新值还是删除了值.

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

从本质上讲,如果你只曾经需要运行构造函数,或转让,有一次,则省略set修改.您不需要它,直接分配集合是针对最佳实践的.

现在,我并不是说永远不会在a上使用setterCollection,有时你可能需要一个,但一般来说你永远不应该使用它们.(serialization当我回到家时,我会发布带有藏品的例子.)

你可以随时使用.AddRange,.Clone等等Collections,你只会失去能力direct assignment.

序列化

最后,如果我们希望SerializeDeserialize包含我们的课程,我们Collectionset怎么办?嗯,总有不止一种方法可以做到,最简单的(在我看来)是创建一个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);
Run Code Online (Sandbox Code Playgroud)

因此,为了解决这个问题,我们可以简单地修改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); } }
}
Run Code Online (Sandbox Code Playgroud)

另一种选择始终是DataContractSerializer(这通常是一个更好的选择.)您可以在此StackOverflow问题上找到有关它的信息.


Eth*_*man 16

使用当前的 VS2019,我们可以简单地执行以下操作:

public List<string> Data { get; } = new List<string>();
Run Code Online (Sandbox Code Playgroud)

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

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

编辑: 正如所指出的,它应该是“=”而不是“=>”(编译器会阻止你使用“=>”)。如果我们使用“public List Data => new List();” 然后它会在每次访问属性时创建一个新列表,这也不是我们想要的。

编辑: 请注意,如果属性的类型是接口,则这将不起作用,例如IList

编辑: 我认为接口的处理是由所使用的序列化器决定的。以下工作完美。我确信所有常见的序列化程序都知道如何处理 ICollection。如果您有一些未实现 ICollection 的自定义接口,那么您应该能够配置序列化程序来处理它,但在这种情况下,CA2227 可能不会被触发,因此在此处无关紧要。(因为它是一个只读属性,你必须在类中分配一个具体的值,所以它应该总是序列化和反序列化一个非空值

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

    [TestMethod]
    public void CA2227_Serialization()
    {
        var test = new CA2227TestClass()
        {
            Data = { "One", "Two", "Three" }
        };

        var json = JsonConvert.SerializeObject(test);

        Assert.AreEqual("{\"Data\":[\"One\",\"Two\",\"Three\"]}", json);

        var jsonObject = JsonConvert.DeserializeObject(json, typeof(CA2227TestClass)) as CA2227TestClass;

        Assert.IsNotNull(jsonObject);
        Assert.AreEqual(3, jsonObject.Data.Count);
        Assert.AreEqual("One", jsonObject.Data[0]);
        Assert.AreEqual("Two", jsonObject.Data[1]);
        Assert.AreEqual("Three", jsonObject.Data[2]);
        Assert.AreEqual(typeof(List<string>), jsonObject.Data.GetType());
    }
Run Code Online (Sandbox Code Playgroud)