【问题标题】:Structs - real life examples? [closed]结构 - 现实生活中的例子? [关闭]
【发布时间】:2010-11-23 20:53:28
【问题描述】:

这里有很多关于 SO 的问题,涉及 C# 中结构和类之间的差异,以及何时使用其中之一。 (一句话回答:如果你需要值语义,就使用结构。)有很多关于如何选择一个或另一个的指导方针,其中大部分归结为:使用一个类,除非你满足这些特定要求,然后使用一个结构。

这一切对我来说都很有意义。

但是,我似乎找不到任何现实生活中人们在系统中使用结构的例子。我是 C# 的(半)新手,我很难想象结构确实是正确选择的具体情况(至少,我还没有遇到过。)

所以,我转向 SO 世界大脑。在哪些情况下,您实际上在一个类不起作用的系统中使用了结构?

【问题讨论】:

  • 我会将你的一句话答案改为“除非你真的知道自己在做什么,否则只使用课程”
  • @Joel - 现在这是我可以接受的规则!
  • @Joel - 这个想法的唯一问题是大多数人只认为他们知道自己在做什么。
  • 获取 Reflector 并查看 .NET 框架本身。您将看到struct 的大量“现实生活”示例。

标签: c# types struct


【解决方案1】:

C# 中的结构的核心无非就是一堆用胶带粘在一起的变量。如果希望特定类型的每个变量代表一堆用胶带粘在一起的独立但相关的变量(例如点的坐标),那么使用暴露字段结构通常比使用类更好,无论是否“一堆”的意思是两个或二十个。请注意,尽管 Microsoft 的 struct-versus-class 建议适用于封装单个值的数据类型,但应认为它不适用于旨在封装独立但相关值的类型。 变量的独立程度越大,使用暴露字段结构的优势就越大。

如果想用一个类来封装一堆自变量,有两种方法可以做到,但都不是很方便。可以使用不可变类,在这种情况下,该类类型的任何非空存储位置都将封装由此标识的实例所持有的值,并且可以将一个存储位置复制到另一个存储位置以使新的存储位置封装那些相同的值。不幸的是,更改存储位置封装的值之一通常需要构造一个新实例,该实例与旧实例一样,只是该值已更改。例如,如果一个人有一个Immutable3dPoint 类型的变量pt 并且希望将pt.X 增加一,则必须执行以下操作:pt = new Immutable3dPoint(pt.X+1, pt.Y, pt.Z); 如果该类型仅封装三个值,也许可以容忍,但很漂亮如果有很多,烦人。

另一种基于类的方法是使用可变类;这通常要求确保类类型的每个存储位置都拥有对该类实例的唯一引用。创建存储位置时,必须构造一个新实例并在其中存储一个引用。如果希望将值从存储位置P 复制到存储位置Q 到另一个,则必须将所有字段或属性从一个实例复制到另一个实例(可能通过让类型实现CopyFrom 方法,并说Q.CopyFrom(P);。请注意,如果有人改为说Q=P;,那似乎可行,但将来修改P 的尝试也将修改Q,反之亦然。可变类可能会起作用,而且它们有时可以效率高,但很容易搞砸。

暴露字段结构结合了不可变类方便的值复制语义和可变类允许的方便的分段修改。大型结构的复制速度比对不可变对象的引用要慢,但修改部分暴露字段结构的成本仅取决于修改的程度,而不是整体结构的大小。相比之下,更改封装在不可变类类型中的一条数据的成本将与总类大小成正比。

【讨论】:

    【解决方案2】:

    我一直在使用结构实现大规模缓存和延迟要求的金融机构工作。基本上结构可以让垃圾收集器完成很多工作。

    查看这些示例:

    http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/ http://00sharp.wordpress.com/2013/07/04/a-case-for-the-structpart-2/

    【讨论】:

      【解决方案3】:

      典型的例子是框架nullable types,例如int?。这些使用结构,因此它们保留了 int 的值语义,但提供了一种方法使它们为 null 而无需装箱并将它们转换为引用类型。

      当您不想通过引用传递某些内容时,您可以使用结构。假设你有一个数据集合,或者你希望通过值传递的对象(即,你传递给它的任何东西都在使用它自己的唯一副本,而不是对原始版本的引用),那么结构是正确的类型使用。

      【讨论】:

      • 我不确定可空类型是否是一个很好的例子。在速度很重要的情况下,通常应该避免使用它们,如果 Nullable<T> 是一个引用类型,它只包含一个类型为 T 的单个字段,它在某些情况下可能比 Nullable<T> 结构慢,但它不会由于空值 Nullable<T> 不是空引用,而是像一个一样的框,因此具有奇怪的语义。
      【解决方案4】:

      我不敢相信没有人提到过XNA:在 XNA 中,几乎一切都是struct。所以当你这样做时

      Matrix rotation = Matrix.CreateRotationZ(Math.PiOver2);
      

      你真的在创建一个值类型。

      这是因为,与应用程序编程不同,垃圾收集器运行时几毫秒的停顿是不可接受的(我们只有 16.6 毫秒来渲染整个帧!),所以我们有尽可能避免分配,这样 GC 就不必运行太多。

      尤其在 Xbox 360 上更是如此,其中 GC 的质量远不及 PC 上的质量 - 即使平均每帧分配一次也会影响性能!

      【讨论】:

        【解决方案5】:

        我不确定这有多大用处,但我今天发现虽然你不能在结构中使用实例字段初始化器,但你可以在类中使用。

        因此下面的代码会产生编译错误,但如果你将“struct”改为“class”,它就会编译。

            public struct ServiceType
            {
                public bool backEnd { get; set; }
                public bool frontEnd { get; set; }
        
                public string[] backEndServices = { "Service1", "Service2" };
                public string[] frontEndServices = { "Service3", "Service4" };
            }
        

        【讨论】:

          【解决方案6】:

          所以我认为您从未使用过 DateTime(结构)。

          【讨论】:

            【解决方案7】:

            我已经在其他地方 (When to use struct in C#) 给出了使用结构的原因,并且出于这些原因,我在实际项目中使用了结构:

            如果我需要在一个数组中存储大量相同的项目类型,我会出于性能原因选择使用结构体,这可能发生在图像处理中。

            需要使用结构在 C# 和 C++ 之间传递结构化数据。

            除非我有充分的理由使用它们,否则我会尽量避免使用它们。

            我知道有些人喜欢使用它们来实现值语义,但我发现这种行为与类的“正常”赋值行为(在 C# 中)是如此不同,以至于人们发现自己遇到了难以追踪的错误,因为一个不记得分配自或分配给的对象具有此行为,因为它是作为结构而不是类实现的。 (这种情况发生在我身上不止一次,所以我给出这个警告,因为我实际上已经被不恰当地使用 C# 结构所烧伤。)

            【讨论】:

            • 有些人抱怨值语义令人困惑,但我很好奇这些人如何避免被类引用可用于封装身份、可变状态或两者都封装的事实所混淆,并且复制引用的效果将取决于所封装的内容。给定SomeClass[100],可能需要至少三种不同的方法来将元素 3 封装的状态复制到元素 4。如何知道在每种情况下使用哪一种,而不是检查所有其他使用的代码数组?
            【解决方案8】:

            我过去创建的一个是 StorageCapacity。它代表 0 字节到 N 艾字节(本可以更高到 yottabyte,但 exa 在当时似乎已经足够了)。自从我在一家存储管理公司工作以来,这个结构就很有意义。你会认为它相当简单:一个带有 StorageUnit(枚举)和一个 Quantity(我使用十进制)的结构。但是,当您添加转换、运算符和类以支持格式化、解析等时,它就会加起来。

            该抽象有助于您获取任何 StorageCapacity 并将其表示为字节、千字节等,而无需多次乘以或除以 1024。

            【讨论】:

              【解决方案9】:

              在某些性能关键的情况下,结构(值类型,因此从堆栈中分配)可能比类(引用类型,因此从堆中分配)更好。 Joe Duffy 的博文“A single-word reader/writer spin lock”展示了这一点的实际应用。

              【讨论】:

                【解决方案10】:

                我认为 .Net 框架非常真实。请参阅“结构”下的列表:

                System Namespace

                【讨论】:

                  【解决方案11】:

                  我通常不关心我的业务应用程序中的“数据密度”。我通常会使用一个类,除非我特别想要 值语义

                  这意味着我正在预见一种情况,我想比较其中两个,并且如果它们具有相同的值,我希望它们显示为相同。对于类,这实际上是更多的工作,因为我需要覆盖 ==、!=、Equals 和 GetHashcode,即使 resharper 为我做了这些,也是多余的代码。

                  所以在我看来,除非你知道你希望这些东西通过值进行比较(在这种情况下是组件值),否则请始终使用类

                  【讨论】:

                    【解决方案12】:

                    我唯一一次使用结构体是在构建 Fraction 结构体时:

                    public struct Fraction
                    {
                       public int Numerator {get;set;}
                       public int Denominator {get; set;}
                       //it then had a bunch of Fraction methods like Reduce, Add, Subtract etc...
                    }
                    

                    我觉得它代表一个值,就像内置的值类型一样,因此如果它表现得像一个值类型,对它进行编码会感觉更自然。

                    【讨论】:

                      【解决方案13】:

                      基本上我尽量不使用它们。我发现他们混淆了团队中的其他开发人员,因此不值得付出努力。我只找到了一个使用它的案例,一种自定义的类 Enum 类型,我们使用代码生成器从 XML 生成。

                      【讨论】:

                        【解决方案14】:

                        我使用了一个结构来表示一个地理位置

                        struct LatLng
                        {
                            public decimal Lattitude
                            {
                                get;
                                set;
                            }
                            public decimal Longitude
                            {
                                get;
                                set;
                            }
                        }
                        

                        这表示单个实体,例如我可以将 2 个 LatLng 加在一起或对这个单个实体执行其他操作。

                        MSDN-struct

                        struct 类型适用于 表示轻量级对象,例如 如点、矩形和颜色。 虽然可以代表一个 点作为一个类,结构更 在某些情况下有效。为了 例如,如果您声明一个数组 1000 个点对象,您将分配 用于引用每个的额外内存 目的。在这种情况下,结构是 更便宜。

                        此外,如果您查看原始类型 Int32,decimal,double..等,您会注意到它们都是 structs,这允许它们成为值类型,同时允许它们实现某些关键接口。

                        【讨论】:

                        • Downvoated,这些示例中的大多数都没有解释为什么您选择结构而不是类。
                        • 简单地聚合单个实体并不是选择结构而不是类的正当理由。您可以同时使用这两种方法,所以我看不出它如何回答这个问题。是的,他确实要求提供示例,但示例应该解释为什么您使用结构而不是类。
                        • +1 因为我用结构做同样的事情!
                        • @Costa Rica Dev,OP 提出了一个问题以查看结构的示例。我们给他看了。有很多 SO 问题可以解释何时以及为什么使用结构。在我的例子中,我使用了一个结构,因为它包含可以代表单个小实体(我提到过)的小信息。
                        • 不知道这也是一所英语学校。 :P
                        【解决方案15】:

                        我经常使用结构来表示可能表示为枚举的域模型值类型,但需要任意数量的离散值,或者我希望它具有无法添加到枚举的其他行为(方法) ... 例如,在最近的一个项目中,许多数据元素与特定的日历月相关联,而不是与日期相关联。所以我创建了一个具有方法的 CalendarMonth 结构:

                        • 静态 CalendarMonth Parse(DateTime inValue);
                        • 静态 CalendarMonth 解析(字符串 inValue);

                        和 TryParse() 方法,

                        • static bool TryParse(string inValue, out CalendarMonth outVal);

                        和属性

                        • int 月份 { 获取;放; }
                        • int 年 { 获取;放; }
                        • DateTime StartMonthLocal { get;放; }
                        • 日期时间 StartMonthUTC{ 获取;放; }
                        • DateTime EndMonthLocal { 获取;放; }
                        • 日期时间 EndMonthUTC { 获取;放; }

                        等等

                        【讨论】:

                          【解决方案16】:

                          Money 结构可能是最常见的结构之一,但电话号码或地址也很常见。

                          public struct Money
                          {
                              public string Currency { get; set; }
                              public double Amount { get; set; }
                          }
                          
                          public struct PhoneNumber
                          {
                              public int Extension { get; set; }
                              public int RegionCode { get; set; }
                              //... etc.
                          }
                          
                          public struct FullName
                          {
                              public string FirstName { get; set; }
                              public string MiddleName { get; set; }
                              public string LastName { get; set; }
                          }
                          

                          请记住,在 .NET 中,您的结构的内存占用量不应超过 16 字节,因为如果它们变得更大,CLR 必须分配额外的内存。

                          另外,因为结构体“活”在堆栈上(而不是像引用类型那样在堆上),如果您需要实例化许多相同类型的对象,您可能会考虑使用结构体。

                          【讨论】:

                          • 哇,你真的搞错了。它是 16 字节,而不是 16 KB... ;)
                          • 哇,你是对的 :) 谢谢!
                          • 我编辑了答案以反映您的更正 Guffa。
                          • @CmdrTallen:当您将引用类型(如字符串)放入结构中时,它是存储在结构中的引用,而不是对象本身。在 32 位系统上,FullName 结构为 12 个字节。
                          • @Stephan,不要使用 double 来表示货币金额,而是使用小数。请参阅十进制的 MSDN。
                          【解决方案17】:

                          好吧,一个类仍然可以为它工作,但我能想到的一个例子是 Point 之类的东西。假设它是一个 x 和 y 值,您可以使用结构。

                          struct Point {
                              int x;
                              int y;
                          }
                          

                          在我看来,我宁愿有一个更简单的整数对表示,也不愿在实际实体没有太多(或任何)行为时定义一个使用具有实例化的类。

                          【讨论】:

                          • 哦,是的 +1 让它变得简单......
                          • 所以你的 x 和 y 是私有变量? :)
                          • Stan:结构体及其成员默认是公开的。
                          • 你确定那个哥斯达黎加开发者?因为不在 C# 中......这就是这个问题被标记为......
                          • @Sandor,你的“观点”到底是什么??
                          【解决方案18】:

                          它们为 Object.GetHashCode() 提供了默认实现,因此当对象是要用作字典键的非引用类型的简单集合时,您可能希望使用结构而不是类。

                          它们对于 PInvoke/interop 或低级网络场景也很有用,您希望精确控制数据结构的二进制布局。 (访问 www.pinvoke.net 获取大量需要结构的互操作代码)

                          但实际上,我自己从不使用它们。不要不使用它们。

                          【讨论】:

                          • 我实际上刚刚在我一直在处理的一些代码中发现了 GetHashCode() 的差异 - 非常酷!我几乎觉得这值得单独提出一个问题(仅供参考)。
                          【解决方案19】:

                          基本上,我使用结构来建模几何和数学数据,或者当我想要基于值的数据结构时。

                          【讨论】:

                            【解决方案20】:

                            对我来说,关键是定义是否要保持对同一个对象的引用。

                            当 struct 是另一个实体的一部分时,这是有道理的,但实体本身也是如此。

                            例如,在上面的例子中,LatLong 是完美的。您需要将值从一个对象复制到另一个对象,而不是一直引用同一个对象。

                            【讨论】:

                              【解决方案21】:

                              结构通常也用于图形/渲染系统。制作点/向量结构有很多好处。

                              Rico Mariani 发布了出色的 quiz on value based programming。他讨论了在特定情况下更喜欢结构的许多原因,并在他的quiz results post 中进行了详细解释。

                              【讨论】:

                              • 喜欢那个链接。 V 有用
                              • 我喜欢那个链接,虽然我建议所有的“规则”违规都可以通过添加一个规则来解决,基本上是说如果一个人想要的东西只会表现得像一组相关但自变量与胶带绑定在一起,不需要任何其他类型的行为,也不需要任何身份概念,通常应该使用结构,尽管有其他“规则”。
                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 2011-12-13
                              • 2012-01-27
                              • 1970-01-01
                              • 2010-09-11
                              • 2016-10-26
                              • 1970-01-01
                              • 2018-12-22
                              相关资源
                              最近更新 更多