C# 结构体:定义、示例、最佳实践和陷阱


C#结构体:从C/C++时代迁移过来的经典。结构体与类相似,也有大量不同之处 。结构体对做为C#开发者来说有很多价值。一般不需要用到结构体,但是有些方面结构体比类做得好。

结构体是什么?

结构体是class对象的小姐妹。初相见,他们之间区别不大。他们都包含不同类型的字段的集合,如下

1 public struct Flower
2 {
3   public string name;
4   protected int beautyRating;
5   private bool isVascular;
6 }

可以试一下结构体。做为一种简单的数据结构,可以将结构体视为一种字段包。可能你会用结构体做为数据传输对象,或者可能你想要在其它基于字段值的地方运行一些逻辑。

也可以用结构体封装行为,如下:

 1  public struct FlowerWithBehavior
 2 {
 3   string name;
 4   int beautyRating;
 5   bool isVascular;
 6 
 7   public void makeBeautiful()
 8   {
 9     beautyRating = 10;
10   }
11 }

到目前为止,结构体看起来像类,只是关键词不同。话虽这样说,但是结构在一些重要方式还是有别于类。

什么使结构与众不同?

最重要的不同之处是:结构体是值类型,类是引用类型。类和结构体相似,如下:

 1 public struct FlowerStruct
 2 {
 3     string name;
 4     int beautyRating;
 5     bool isVascular;
 6 
 7     public FlowerStruct(string name, int beautyRating, bool isVascular)
 8     {
 9         this.name = name;
10         this.beautyRating = beautyRating;
11         this.isVascular = isVascular;
12     }
13 }
14 
15 public class FlowerClass
16 {
17     public string name;
18     public int beautyRating;
19     public bool isVascular;
20 
21     public FlowerClass(string name, int beautyRating, bool isVascular)
22     {
23         this.name = name;
24         this.beautyRating = beautyRating;
25         this.isVascular = isVascular;
26     }
27 }

尽管看起来相似,但是类和结构体有不同之处:

 1 public void Test_Struct_Vs_Class_Equality()
 2 {
 3     FlowerStruct struct1 = new FlowerStruct("Daisy", 3, true);
 4     FlowerStruct struct2 = new FlowerStruct("Daisy", 3, true);
 5 
 6     FlowerClass class1 = new FlowerClass("Daisy", 3, true);
 7     FlowerClass class2 = new FlowerClass("Daisy", 3, true);
 8 
 9     Assert.Equal(struct1, struct2);
10     Assert.NotEqual(class1, class2);
11 }
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Drawing;
 6 using System.Windows.Forms;
 7 using Microsoft.VisualStudio.TestTools.UITesting;
 8 
 9 namespace Struct_And_Class
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             FlowerStruct struct1 = new FlowerStruct("Daisy",3,true);
16             FlowerStruct struct2 = new FlowerStruct("Daisy", 3, true);
17 
18             FlowerClass class1 = new FlowerClass("Daisy",3,true);
19             FlowerClass class2 = new FlowerClass("Daisy", 3, true);
20 
21             bool Result1 = Equals(struct1,struct2);
22             bool Result2 = Equals(class1,class2);
23 
24             Console.WriteLine("结构体对比结果是{0}",Result1);
25             Console.WriteLine("类的对比结果是{0}",Result2);
26         }
27     }
28 
29     public struct FlowerStruct
30     {
31         string name;
32         int beautyRating;
33         bool isVascular;
34 
35         public FlowerStruct(string name, int beautyRating,bool isVascular)
36         {
37             this.name = name;
38             this.beautyRating = beautyRating;
39             this.isVascular = isVascular;
40         }
41     }
42 
43     public class FlowerClass
44     {
45         string name;
46         int beautyRating;
47         bool isVascular;
48 
49         public FlowerClass(string name, int beautyRating, bool isVascular)
50         {
51             this.name = name;
52             this.beautyRating = beautyRating;
53             this.isVascular = isVascular;
54         }
55     }
56 }
完整代码

 

 如上面完整代码所呈现的,对于结构体来说,只要是内部是相等相同的,结果就是相等的;而类不相等,与字段没有关系。

这或许是结构体与类之间最大的不同之处。用struct,可以实现拆箱操作,可以创建值类型对象。

同样,结构体永远不是null

1 public void Test_Struct_Not_Null(
2 {
3     Flower flowerStruct = null; //<-- Compile error.
4 }

空引用错误是软件中的Bug原因之一。

当然,可以欺骗一下,使它们成为可空类型(nullable)。

注意构造函数的挑战

冷静一下,结构体不是完美的。如果你想要结构体做为不可改变的值对象,如下面示例所示,需要一个无参数的构造函数。这意味着你的值对偶处在一种无效状态。

 1 public struct StrictFlower
 2 {
 3     public string name;
 4     public int beautyRating;
 5 
 6     public StrictFlower(string name, int beautyRating)
 7     {
 8         this.name = name;
 9         if (name == "daisy")
10             this.beautyRating = beautyRating;
11         else
12             this.beautyRating = 5;
13     }
14 }
15 
16 [Fact]
17 public void Struct_Always_Has_Parameterless_Constructor()
18 {
19     StrictFlower flower = new StrictFlower();
20     flower.name = "daisy";
21     int notFive = 3;
22     flower.beautyRating = notFive;
23 }

由于这样的原因,你不能担保类在初始化时就是有效的,如果你想要保证你的结构体一直处于一个有效状态,你需要让你的团队避免使用无参构造函数。

注意继承限制

结构体自动继承自ValueType类,ValueType类继承自object(CSDN:ValueType为值类型提供基类)。但是不能继承自他们:

1 public struct InheritingFlower : Flower //<- Compile Error
2 {
3 }

尽管如何,可以使用接口继承:

 1 public struct ImplementingFlower : IComparable<ImplementingFlower>
 2 {
 3     string name;
 4     int beautyRating;
 5 
 6     public int CompareTo(ImplementingFlower other)
 7     {
 8         return beautyRating.CompareTo(other.beautyRating);
 9     }
10 }

使用结构体的最佳方式

结构体做为一种简单的数据结构很闪亮。很容易玩转struct结构体,当需要代表数据传输对象,数据输入对象、数据输出对象,或者其它信息持有者。当我们将它们做为一种简单的结构形式,没必要封闭在属性里面。

 1 public struct PropertyFlower
 2 {
 3     string name;
 4     int beautyRating;
 5     bool isVascular; 
 6 
 7     public string Name { get => name; set => name = value; }
 8     public int BeautyRating { get => beautyRating; set => beautyRating = value; }
 9     public bool IsVascular { get => isVascular; set => isVascular = value; }
10 }

做为替代,可以让字段自由运行,如下:

1 public struct FieldFlower
2 {
3     public string name;
4     public int beautyRating;
5     public bool isVascular;
6 }

一定要避免将结构体做为collecting parameter使用。如果将结构体传递给了方法函数,它是值传递,传递的是struct的副本,不是struct的指针。

 1 class StructPass
 2 {
 3     public void changeName(Flower flower)
 4     {
 5         flower.name = "different";
 6     }
 7 }
 8 
 9 [Fact]
10 public void Structs_Pass_By_Value()
11 {
12     Flower flower = new Flower("daisy", 3, true);
13     new StructPass().changeName(flower);
14 
15     Assert.NotEqual("different", flower.name);
16 }

结构体是类的小兄弟,他们不与其它类一起出现,他们较少用到,但是他们有他们的用处。

结构体具有内置的平等性。他们不能为null,结构体以最简单的方式制作出简洁的结构数据。如果谨慎使用结构体,会物超所值。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM