【问题标题】:Is it right to use a struct instead of giving a constructor 8 arguments?使用结构而不是给构造函数 8 个参数是否正确?
【发布时间】:2014-04-19 11:49:23
【问题描述】:

我只是在想,

如果我需要为 Apartment 类编写构造函数, 而公寓类有几个属性,如ApartmentSizeNumberOfBedroomsNumberOfShowersCurrentMarketValuePurchasePrice等。

创建一个称为ApartmentProperties 的结构并将所有上述属性作为该结构的成员是否更具可读性,所以当我需要构建一个公寓时,所有构造函数需要的是只有这个结构?

如果不是,那有什么更优雅的方法呢?

【问题讨论】:

  • 是的,当您必须传递许多相关参数时,这样的包装类是一个很好的方法。
  • 不给构造函数提供参数,并通过属性设置值,如果你不设置它们,在每种情况下都使用适当的默认值是不是不合理?
  • @ClickRick 您的意思是构造函数将所有数据成员设置为 0 和空值,然后我将使用 setter 设置实际值?
  • 为什么是 struct 而不是 class ?值类型(又名结构)应该与少数属性一起使用(2-3)...... 8 对我来说似乎有点太多......
  • 你所做的在软件工程中被称为凝聚力,所以结合不同的相关属性,如购买价格、销售价格,并制作它们的结构或类,因为这将是更易于维护的代码。

标签: c# oop struct readability


【解决方案1】:

看起来有点圆。你有一个对象,它的构造函数需要所有这些值,所以你正在创建一个对象来包含它们。所以现在你有了一个需要所有这些值的结构......你如何创建这个结构?

你提出这个:

ApartmentProperties props = new ApartmentProperties(size, numBedrooms, numShowers, marketValue, purchasePrice);
Apartment apt = new Apartment(props);

这样做似乎没有什么不同:

Apartment apt = new Apartment(size, numBedrooms, numShowers, marketValue, purchasePrice);

如果这是您唯一一次使用 ApartmentProperties 结构,而您所做的只是将所有这些值放入其中,将其传递给构造函数,然后再次将它们拉出,那么感觉没有必要.这只是另一个需要考虑的对象。

在 cmets 中,有人建议如果您不喜欢在构造函数中有这么多参数,那么您可以这样做:

Apartment apt = new Apartment();
apt.Size = size;
apt.NumBedrooms = numBedrooms;
apt.Numshowers = numShowers;
apt.MarketValue = marketValue;
apt.PurchasePrice = purchasePrice;

我不是这种方法的忠实拥护者。现在你的构造函数更小更整洁了,是的,但你所做的只是将整齐地包裹在其中的逻辑移到你调用它的任何地方。这使得调用代码负责确保正确分配所有属性。我建议将分配留在构造函数中 - 它只是让您假设 Apartment 对象已分配其属性,因为您知道构造函数会完成它。

当然,有一个中间立场。您可以取出 一些 构造函数参数,只留下那些 必须 存在的参数。例如,一套公寓总是有大小、卧室数量和淋浴数量,但您可能不知道它的当前价值或购买价格——也许有人已经在那里住了一段时间并且没有' t 最近有估价。在这种情况下,您可以将构造函数缩减为仅三个参数,并允许调用代码分配其他已知的if。或者更好的是,用不同数量的参数重载构造函数。只需确保您有某种方式可以确定是否已分配值(例如,可能存在一些奇怪的情况,其中销售价格为零是有效的,但也许您知道 -1 永远不会有效)。如果您需要非常确定,请使用Nullable<T>,如果尚未分配则返回 null。

【讨论】:

  • +1,看到您的示例代码时,我放弃了我的答案。这就是我要说的。
  • 你也可以使用对象初始化器:var apt = new Apartment { Size = size, NumBedrooms = numBedrooms, NumShowers = numShowsers, MarketValue = marketValue, PurchasePrice = purchasePrice };。为您节省一些击键和“组”一起初始化的属性。
  • 这也可以,但它需要你有一个无参数的构造函数,这会遇到我提到的将初始化留给调用代码的问题。如果您想保证 Apartment 实例将设置某些值(大小、房间数等),那么您需要将无参数构造函数保留为私有。
  • 你为什么说对象初始化器需要一个无参数的构造器?这不是真的,你可以写new Foo(para1) { PropertyB = "HelloWorld", };
  • @Quaker:一般来说,是的。替代方案要么将过多的逻辑从构造函数中移出到您对其控制较少的地方,要么最终导致重复工作。如果该特定的值集合将被传递给许多其他东西,那么也许这将证明有一个容器是合理的,但在这里它只是不必要的复杂性。如果你想提高可读性,也许可以格式化你的代码,使参数形成一个垂直列表,并确保传递给它们的参数和变量的命名是不言自明的。
【解决方案2】:

首先,我建议您完全不要使用struct。而应该使用class

Value Type Usage Guidelines 声明

建议您为满足以下任一条件的类型使用结构:

  1. 表现得像原始类型。
  2. 实例大小小于 16 个字节。
  3. 是不可变的。
  4. 值语义是可取的。

出于性能原因,8 个字段对于结构来说太大了。另请参阅this 了解更多信息

【讨论】:

  • 这些准则适用于应该表现得像对象的结构。然而,如果想要的不是一个“对象”,而是一堆用胶带粘在一起的变量,那么应该使用一堆用胶带粘在一起的变量,而不是试图用一个对象来模拟一堆变量用胶带粘在一起,或者包装一堆变量,这些变量用胶带粘在一起,以模拟一个对象,模拟一堆用胶带粘在一起的变量。
【解决方案3】:

您可以使用无参数(默认)构造函数,并依赖类型初始化器同步税来初始化属性:

public class Apartment
{
    public Apartment()
    {
        // set default values
    }

    // define properties
    public int NumberOfBedRooms { get; set; }
    // ...
}

var apt = new Apartment {
  ApartmentSize = 100,
  NumberOfBedrooms = 3,
  NumberOfShowers = 2,
  CurrentMarketValue = 100000m,
  PurchasePrice = 100000m
}

这样您就不必编写构造函数重载,并且您的类的用户可以确定要初始化的内容。

【讨论】:

    【解决方案4】:

    结构的核心是一堆用胶带粘在一起的变量。具有私有字段的结构可能会尝试表现得像其他东西,但是具有暴露公共字段的结构会表现得像一堆用胶带粘在一起的变量。如果想用胶带将一堆变量粘在一起,暴露字段结构通常是完美的选择。

    请注意,Microsoft 的指导方针假定代码希望所有内容都表现得像 Object;它们也是在 .NET 支持泛型类型之前编写的,当时通常无法将事物存储在集合中,除非先将它们转换为 Object。如果一个人想要某种语义与Object(*) 的派生词一致的东西,那么遵循这些指导原则通常是件好事。但是,如果一个人想要的只是有一个存储位置(字段、变量等),可以将一堆变量用胶带粘在一起,则不应遵循它们。当应用于特定场景时,许多准则实际上是落后的。

    (*) 结构类型存储位置不包含Object 的派生,也没有对其中的引用;相反,它保存结构的公共和私有字段的连接内容。如果将结构类型转换为引用类型,系统将创建一个新的堆对象,其字段与结构的字段匹配,并将结构的字段复制到新对象的字段;然后,生成的对象将在很大程度上表现为一个类对象,其字段和方法与结构的字段和方法相匹配。如果结构不遵循 MS 准则,则存储位置类型和堆类型将表现不同; MS 认为这总是一件坏事,但是如果想要一个存储位置来保存一堆用胶带粘在一起的变量,那么拥有一个行为清晰的结构(类不能做的事情)比一个结构更有用假装自己是一个类对象,正在尽力(相当笨拙)模拟这样的事情。

    将结构作为值参数传递与单独传递结构的所有公共和私有字段的成本大致相同。将结构作为引用参数(refout)传递具有固定成本,无论字段数量如何。在结构体类型的可变存储位置调用实例方法,相当于调用在其参数列表开头带有额外参数ref StructType this的方法;如果尝试在临时或不可变位置调用someStruct.Foo(),编译器将默默替换var temp = someStruct; temp.Foo();;因为(仍然!)没有办法告诉编译器这种替换不能安全地应用于特定方法,所以通常应该避免编写这种替换不安全的方法。

    关于您的特定场景,我认为一个简单的暴露字段结构可能几乎是完美的。它允许以任何方便的顺序设置参数值,使构造具有相似但不相同参数的多个对象等变得容易。从性能的角度来看,将结构作为ref 参数有时可能会快一点比按值传递,但除非多次传递相同的结构,否则我希望按值传递应该没问题(如果您使用暴露字段结构,我希望 JITter 可能会生成大致相当于传递的代码每个结构字段作为单独的参数)。

    【讨论】:

    • 其实我想了很多。有人建议我按原样提供参数列表或用类包装它。这很奇怪,因为用 class 包装它意味着该类将通过引用传递,而传递值本身将通过值传递它们。按值传递结构,还是通过引用传递结构所包含的元素的成本相同?
    • @Quaker:按值传递结构大致相当于按值传递所有元素。通过引用传递任何东西的成本通常比通过值传递整数略高。我不确定“通过引用传递结构所包含的元素”是什么意思。另外,请注意,类不是“通过引用传递”;相反,类对象引用通常通过值传递(在少数情况下,类对象引用可以通过引用传递,但这仍然与通过引用传递类不同)。关于后一点...
    • @Quaker:我建议每当你听到“对象引用”时,想想“对象标识符”,事情就会清楚得多。当一个方法接受一个类类型参数时,它会被传递一些东西来识别该类类型的对象(或者为空)。接收通过引用传递的参数的方法一旦离开范围就不能持久化引用。但是,按值接收对象标识符的方法可以存储该标识符以供以后使用——其行为与“按引用传递”语义非常不同。
    【解决方案5】:

    我建议你这样做:

    你应该像这样创建一个类:

    public class MyClass
    {
        ...
        public int ApartmentSize{get;set;}
        ...
    }
    

    在 Apartment 类的构造函数中,这样做:

    Apartment(MyClass obj)
    {
      // access the values like: obj.ApartmentSize  
    }
    

    Struct 对此太过分了

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-31
      • 1970-01-01
      • 2017-05-10
      • 1970-01-01
      • 2021-12-25
      相关资源
      最近更新 更多