我一直想知道这个问题,并且未能找到关于这个问题的很多评论,而且我自己也未能得出结论。
在创建对象时,最好的做法是尽可能限制对象在公开数据和允许操作数据方面。如果你可以使一个对象不可变,特别是在多线程应用程序中,那么最好这样做。
话虽如此,C#似乎更倾向于不遵循此规则的开发人员,因为允许类的定义更简单,更重要的是,更容易维护。
采用以下只读不可变类:
public class ActiveDirectoryUser
{
private readonly string firstName, lastName, office, username, email;
public ActiveDirectoryUser(string firstName, string lastName, string office, string username, string email)
{
this.firstName = firstName;
this.lastName = lastName;
this.office = office;
this.username = username;
this.email = email;
}
public string FirstName
{
get { return firstName; }
}
public string LastName
{
get { return lastName; }
}
...
public string Email
{
get { return email; }
}
}
并将其与下面更简单的示例进行比较,该示例不是只读的。
public class ActiveDirectoryUser
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
...
public string Email
{
get;
set;
}
}
可以通过以下方式实例化:
ActiveDirectoryUser user =
new ActiveDirectoryUser
{ FirstName= "Sam", LastName = "Shiles", ..., Email ="Sam@shiles.com" };
鉴于定义更简单,代码行越少,开发人员错误的可能性越小,另一个开发人员可以轻松理解代码(特别是对于比我们的简单示例更真实的示例),是值创建适当的只读,不可变对象,值得花费?
此外,其他人认为使用自动esq语法可以使用C#创建不可变对象,例如:
public string FirstName {get; readonly set;}
答案 0 :(得分:8)
鉴于定义更简单,代码行越少,开发人员错误的可能性越小,另一个开发人员可以轻松理解代码(特别是对于比我们的简单示例更真实的示例),是值创建适当的只读,不可变对象,值得花费?
是
我注意到没有什么能阻止你说
int Foo { get; private set; }
然后仅在构造函数中使用setter,以保证属性的不变性。你只是没有得到漂亮的对象初始化器语法,但你可以很容易地创建一个构造函数。
此外,其他人是否认为使用自动esq语法可以使用C#创建不可变对象...
是。特别是,C#语言设计团队认为。
C#3.0增加了许多功能,使C#成为一种“不可变的友好”语言:匿名类型,查询理解和表达式树特别鼓励不可变数据“功能”编程风格。它还添加了一些功能,使C#成为一种“可变性友好”的语言:自动属性,对象初始化器和类型推断的数组都会浮现在脑海中。
语言设计团队非常清楚,后面的这些特性使得在DLR团队制作一个庞大的不可变表达式树类型库的同时,更容易创建可变对象。我可以向你保证,讽刺并没有丢失。
在语言设计团队中有许多提议可用于制作不可变对象的更好的语法糖。事实上,需要可变性的对象初始化器具有与匿名类型初始化器几乎相同的语法,这是不可变的,是探索的明显起点,但不是唯一的。
所有这一切,当然,首先,我不再代表语言设计团队发言,其次,在Roslyn项目之外没有公布未来的C#版本。因此,关于该语言的假设未来版本的可能特征集的推测仅仅是:我的推测。
答案 1 :(得分:3)
是创建正确的只读,不可变对象的价值,值得花费吗?
这取决于您对不变性,线程安全性的关注程度以及您对其他开发人员不会误解的程度。
我会问相反的是 - 可能的线程错误是否值得创建可变对象的价值非常快而且简单?
答案 2 :(得分:1)
当我思考这些问题时,我倾向于将代码划分为几个类别。
在应用程序级别,您的类通常是叶类,旨在实现项目范围内的特定功能。它们很少是重用的候选者,它们的唯一依赖是应用程序本身的其他特定类。
在这里,我允许自己一些自由,我会说几个公共财产不会伤害任何人。无论如何,它都是非常本地化的,随着应用程序的更改,您仍然需要了解代码的交互方式以对此代码进行任何更改。但是,自由并不等于草率。
在Framework级别,我的意思是在项目之间高度可重用的子系统。可能是您的salt +哈希密码类已被证明可以使用多年。或者也许是您在几个项目中依赖的整个第三方产品的包装。
一旦引用了这样的程序集,或者如果将源文件添加到解决方案中,您的应用程序仍然可以被视为该代码的唯一使用者。但该代码仍然必须非常包含,并应承诺遵守合同。如果公共属性允许对象具有损坏状态,我会说这是一个相当蹩脚的框架。
在Api级别,事情发生了巨大变化。现在,您正在设计应该由许多订阅者随时间使用的代码。只是不要混淆术语,这里显而易见的候选人是.NET Framework本身。但如果你是提供API的人,人们甚至可以支付保修费;你真的想要一个不容易被滥用的稳定系统。
我认为有趣(并且有点有趣)是来自OOP纯粹主义者和SOLID纯粹主义者(以及其他纯粹主义者)的所有良好原则似乎默认为他们所有代码都在那么高的假设 - 支持api级别。
所以为了回答你的问题,我会给你几个问题来考虑
有一个明确的例子。在一个我非常自豪的Web项目中,很多数据都是在单例中缓存的(静态类真的是)。我们确实正确锁定以确保GetOrSet上的线程安全。除了一个小细节。我们将对缓存中实际对象的引用分发给多个线程。它们是可变的,任何写入其公共属性的尝试都可能导致各种问题。现在我们克隆它们吗?让他们一成不变?复制这些对象会降低性能,以免造成伤害吗?不。由于我们是自己缓存的唯一消费者,我们只是决定将来自服务的所有数据视为只读。当我们需要副本时,我们使用Automapper。我们在代码审查期间检查了这一点。它工作得很好。虽然我们有一些错误,但还没有一个是由写入可变对象引起的。
病人: - 当我这样跳时,我的腿受伤了。
医生: - 那就不要那样跳!答案 3 :(得分:0)
尽管你的第二个例子是不正确的(它不是不可变的,你应该添加一个带有与你的字段匹配的参数的构造函数)我会说它可以是readonly修饰符表示你在编写类时的意图。这意味着将来当其他人修改课程时,他们会知道这些字段只是读取而不仅仅是有效的只读。