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