是否可以创建不可变对象而不传递构造函数中的所有内容?

时间:2012-08-14 15:15:41

标签: c#

我想让我的课不可变。显而易见的方法是将所有字段声明为get; private set;并初始化构造函数中的所有字段。所以客户必须在构造函数中提供所有内容问题是,当构造函数中有大约10个或更多字段传递它们变得非常难以理解时,因为每个字段都没有标签。

例如,这很可读:

info = new StockInfo
        {
            Name = data[0] as string,
            Status = s,
            LotSize = (int)data[1],
            ISIN = data[2] as string,
            MinStep = (decimal)data[3]
        };

与此相比:

new StockInfo(data[0] as string, s, (int) data[1], data[2] as string, (decimal) data[3])

现在成像我有10个或更多参数。

那么如何才能使类不可变的保存可读性?

我建议在使用构造函数时只使用相同的格式:

info = new StockInfo(
            data[0] as string,           // Name
            s,                           // Status
            (int)data[1],                // LotSize
            data[2] as string,           // ISIN
            (decimal)data[3]             // MinStep
       );

你能提出更好的建议吗?

6 个答案:

答案 0 :(得分:8)

以下是使用C#命名参数的方法:

var info = new StockInfo
(
    Name: data[0] as string,
    Status: s,
    LotSize: (int)data[1],
    ISIN: data[2] as string,
    MinStep: (decimal)data[3]
);

答案 1 :(得分:7)

以下是一些选项。你必须决定什么是最适合你的:

使用带有命名参数的经典不可变对象(带有大量构造函数)以提高可读性。 (缺点:有些人不赞成使用许多构造函数参数。如果不支持命名参数,可能不方便使用其他.NET语言。)

info = new StockInfo
           (
             name: data[0] as string,
             status: s,
             ...
           )

通过不可变接口公开可变对象。 (缺点:对象仍然可以通过强制转换进行变异。要写入额外的类型。)

public interface IStockInfo
{
   string Name { get; }
   string Status { get; }
}

IStockInfo info = new StockInfo
                      {
                          Name = data[0] as string,
                          Status = s,
                          ...
                      }

公开可变对象的只读视图 - 例如,请参阅ReadOnlyCollection<T>。 (缺点:实现额外的类型。创建额外的对象。额外的间接。)

var readOnlyInfo = new ReadOnlyStockInfoDecorator(info);

公开可变对象的不可变克隆。 (缺点:要实现的额外类型。创建了额外的对象。需要复制。)

var immutableInfo = new ImmutableStockInfo(info);

使用 freezable objects 。 (缺点:冻结后的突变 - 尝试在执行时才会被捕获。)

info.Freeze();
info.Name = "Test"; // Make this throw an exception.

使用流畅式构建器或类似的(缺点:有些人可能不熟悉模式。需要编写大量额外代码。创建了大量副本。中间状态可能是非法的)

info = StockInfo.FromName(data[0] as string)
                .WithStatus(s) // Make this create a modified copy
                .WithXXX() ;

答案 2 :(得分:6)

不,这是不可能的。您可以使用不可变对象,或者希望能够修改对象。

  1. 您可以使用命名参数。
  2. 您可以考虑传递其他对象(和组参数),这样一个对象将只包含非常相似的参数。
  3. 查看您的代码我可能还建议您首先提取参数,因此不要传递data[0] as string之类的内容,而是使用string stockName = data[0] as string;,然后使用stockName。这应该会使您的代码更具可读性。

    如果您将如此多的参数传递给对象的构造函数,那么修改您的设计可能是个好主意。您可能违反了Single Responsibility principle

答案 3 :(得分:1)

  

那么如何才能使类不可变的保存可读性?

您可以使用named parameters

info = new StockInfo(
        name: data[0] as string,
        status: s,              
        lotSize: (int)data[1],  
        isin: data[2] as string,
        minStep: (decimal)data[3]
   );

请注意,使用对象初始值设定项的目标不是可读性 - 并且它们不应被视为构造函数的替代。总是在构造函数中包含正确初始化类型所需的每个参数是一个非常好的主意。不可变类型必须在构造期间通过构造函数或工厂方法传入所有参数。

对象初始化器永远不会使用不可变类型,因为它们通过在构造函数之后设置值来工作。

答案 4 :(得分:0)

更多可能的解决方案:

按惯例的不变性

对象是可变的,你只是表现良好,并且在设置之后永远不会改变它。这对于大多数用途(任何对象在开始时是公开的)都是完全不合适的,但是对于使用它们的地方数量有限的内部“工人”对象可以很好地工作(因此可以在有限数量的地方使用它们)陷入困境并改变它们。)

更深层次结构

假设你的真正的类有超过5个字段(不是那么难读,特别是如果你有一个带有工具提示的IDE),有些可能是可组合的。例如,如果您在同一个班级中有不同的名称,地址和纬度和经度部分,您可以将其分为名称,地址和协作类。

在某些此类案件中发生的奖励是,如果你有很多(我的意思是很多,这是值得的,任何少于几千,这是浪费时间)这些对象和它们之间有一些相同的字段,你有时可以用这样的方式构建它们,使得这些共享值在每种情况下都具有相同的对象,而不是不同的相同对象 - 所有可能出现别名错误的事情都可以'发生了,因为它们毕竟是不变的。

构建器类

示例包括StringBuilderUriBuilder。在这里你已经得到了你所拥有的问题 - 你想要不变性的好处,但至少有一些时候你想要能够在一个以上的步骤中构建对象。

所以你创建了一个具有等效属性的不同可变类,但是使用setter和getter,以及其他变异方法(Append()之类的东西是否有意义取决于当然的类),以及方法构造一个不可变类的实例。

我已经在构造函数有多达30个参数的类中使用了这个,因为确实有30个不同的信息是同一个问题的一部分。在这种情况下,关于我调用构造函数的唯一地方是在相应的构建器类中。

答案 5 :(得分:0)

我建议为“may-mutable”对象提供一个包含AsMutableAsNewMutableAsImmutable方法的界面可能会有所帮助。一个不可变对象可以通过简单地返回自己来实现AsImmutable。可变对象应该通过返回使用可变对象创建的新不可变对象作为构造函数参数来实现AsImmutable,或者返回已知等效的不可变对象。不可变对象的构造函数应该使用原始内容加载新对象,但是在所有可能可变字段上调用AsImmutable

一个可变对象应该简单地返回自己以响应AsMutable,而一个不可变对象应该构造一个新的“浅”可变对象。如果代码希望改变“maybe-mutable”属性引用的对象,则应将该属性设置为其AsMutable等效值。

在不可变对象上调用AsNewMutable方法应该像AsMutable一样。在可变对象上调用它可以与AsImmutable.AsMutable等效,也可以创建任何嵌套可变对象的可变克隆(有时候任何一种方法都可能更好,这取决于哪些嵌套对象最终会被突变)。

如果你使用这种模式,你应该能够获得两个不可变对象的许多好处(最值得注意的是,能够拍摄“深”对象的快照而不需要制作“深度”副本)和可变的(能够通过在同一个实例上执行许多步骤来生成对象)。通过让每个可变对象保持对不可变对象的引用可以增强性能,该对象的状态在某个时刻与它自己的状态相同;在构造不可变实例之后,该对象可以检查它是否与另一个匹配,如果是,则丢弃新实例并返回旧实例。虽然这似乎代表了额外的工作,但事实上,在可变对象AsImmutable在突变之间不止一次地调用它的情况下,它实际上可以大大提高性能。如果在树的大部分树实际上没有被变异的情况下在深树结构上调用AsImmutable两次,那么让树的非变异部分返回相同的对象实例,这将有助于将来的比较。

注意:如果使用此模式,则应该为深度不可变类型覆盖GetHashCodeEquals,但不应覆盖可变类型。具有相同值的不可变对象应该被认为是可互换的,因此是等价的,但是可变对象不应该等同于任何东西,而不管它们的值如何。另请注意,如果对象包含doublefloatDecimal类型的任何内容,则可能需要谨慎,因为这些类型会覆盖Object.Equals以表示等价以外的其他内容。< / p>