C#构造函数与自动属性及对象初始化器的比较

8
我经常使用自动属性,但现在我越来越倾向于设置只读的后备字段,并在构造函数中初始化。我删除所有的setter,只有在属性确实需要setter时才添加后备字段。
我发现这样可以使我的类更加健壮和优雅,也更符合面向对象的原则,我为自己以前没有这样做而感到遗憾。
我认为构造函数在C#代码示例中被普遍地低估了,我认为自动属性和对象初始化器是其中的一部分。因此,我的问题是:为什么C#团队要推出这样的特性,而不是更专注于推广最佳实践呢?总的来说,我认为编写糟糕的代码太容易了,我相信可以做得更多来帮助程序员编写好的代码。

并非所有类型都必须是不可变的。在构造函数中使用可变类型,将其复制到私有成员变量中是非常普遍和有益的。这样,它就能被外部代码保护免受更改(假设对于任何可变依赖项都执行相同的操作)。这尤其适用于简单数据对象。 - Merlyn Morgan-Graham
3个回答

13

从对话中,我相信C#团队已经认识到他们使编写可变类型更加容易,而未为不可变类型提供类似的好处。并不是说随着时间的推移,他们让不可变性更加困难 - 除了匿名类型之外,它们是不可变的,但具有各种其他缺点。当然,我不希望自动属性被取消 - 在适当的情况下,它们非常有用。我只是希望有一个等价的只读属性(只允许在构造函数中设置)。

然而,我发现C# 4的命名参数和可选参数使得构建不可变类型实例更加容易 - 你仍然可以获得很多对象初始化器的好处,而没有可变性的缺点。只需为类型的可选方面提供默认值,将其余部分作为必填构造函数参数,并且调用方可以按照自己的意愿使用命名参数来增加清晰度。

不幸的是,集合初始化器是一个更难解决的问题。我希望能够使用“链式”初始化器来与不可变集合一起工作,这样就不需要重复地在同一个实例上调用Add,编译器可以创建链接在一起的Plus调用:

ImmutableList<string> x = new ImmutableList<string> { "a", "b", "c" };

会前往:

ImmutableList<string> x = new ImmutableList<string>().Plus("a")
                                                     .Plus("b")
                                                     .Plus"(c");
当然,一开始就有更多的不可变集合会很好 :) 当然,这些都无法帮助自动属性。我必须承认,最近我有点作弊,使用私有setter来伪造不可变性:
public string Name { get; private set; }

虽然我有真正的意图,但这让我感觉很不舒服,因为它并没有使它变得真正不可变。

基本上,我是说我理解你的痛苦 - 我很确定C#团队也是这样。请记住,他们的资源有限,设计一门语言是非常困难的。

你可能会发现来自NDC 2010的视频很有趣 - 那里有一个由Eric Lippert,Mads Torgersen,Neal Gafter(还有我)组成的重要小组讨论,而我的C# 5提案则在另一个视频中。


如果不可变性更容易实现,那肯定是很好的。要保证完全的不可变性,你必须完全控制所有包含的类。例如,你说匿名类型是不可变的,但我添加了一个数组和一个可变列表,并成功修改了它们。如果你有一个公开可访问的成员今天是不可变的,明天就可能变成可变的,这将导致你的不可变性被破坏。 - Merlyn Morgan-Graham
谢谢,我会看一下那些视频。阅读更多后,似乎 F# 以更好的方式处理这些事情,通过默认锁定对象并在需要时允许可变性。我猜遗留问题阻碍了 C# 团队朝着这个方向发展。Jon,你对 F# 和 C# 有什么想法? - terjetyl
@TT:我在F#方面的经验不够丰富,但我确实喜欢F#所做的各种事情。我提出的关于C# 5的许多建议都是基于F#的。 - Jon Skeet
我完全同意您的私有设置。 对我来说,只需输入 propg + tab + tab,而不是创建适当的只读备份字段,这太容易了。毫无疑问,“public string Name { get; readonly set; }”(尽管听起来很奇怪)会进入C#5吧? - Alex Humphrey
@Alex:这正是我之前提出的 :) 我不知道它是否会进入C# 5。希望我们能在不久的将来开始看到第一批成果。 - Jon Skeet

1
我删除了所有的setter,只有在属性明确需要setter时才添加。 我发现这样可以使我的类更加健壮和优雅的面向对象。
我完全同意你的看法。我曾经遇到过遗留代码,其中有很多对象初始化器用于某些类层次结构。我需要添加一些属性,然后我就头疼了,因为要找到构造类实例的所有地方。 第一次提交后,现在我需要再添加一个属性。这太疯狂了!
为了限制对象初始化器的使用,我删除了无参构造函数。

0
我发现在c#代码示例中,构造函数往往被极少使用,我认为自动属性和对象初始化器是其中一个很大的原因。
如果您的对象有很多属性,您显然不想从构造函数中初始化它们所有。需要传递4或5个以上的参数对于可读性来说是相当糟糕的(即使Intellisense使其易于编写)。而且,如果您只想初始化一些属性并使用其他属性的默认值,您要么需要许多构造函数重载,要么必须将这些默认值明确地传递给构造函数。
在这种情况下,对象初始化器非常方便,只要属性不是只读的(但正如Jon指出的那样,C#4中的可选参数是一个很好的替代品)。
关于“为什么C#团队推出这样的功能而不是更专注于提供最佳实践”,我认为对象初始化器是必需的Linq:没有它们,您无法创建匿名类型。至于自动属性,它们不是很关键,但可能很容易实现,并且可以节省封装字段的属性所需的时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接