C# 结构体 Struct

在 C# 中,struct 是表示数据结构的值类型数据类型。 它可以包含参数化构造函数、静态构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型。

struct 可用于保存不需要继承的小数据值,例如 坐标点、键值对和复杂的数据结构。

结构体声明

使用 struct 关键字声明结构。 默认访问修饰符是internal

以下示例声明了一个结构 Coordinate。

struct Coordinate
{
    public int x;
    public int y;
}

可以使用或不使用 new 运算符创建结构对象,与原始类型变量相同。

var point = new Coordinate();
Console.WriteLine(point.x); //输出: 0  
Console.WriteLine(point.y); //输出: 0  

上面,使用 new 关键字创建了 Coordinate 结构的对象。 它调用结构的默认无参数构造函数,该构造函数将所有成员初始化为其指定数据类型的默认值。

如果在不使用 new 关键字的情况下声明 struct 类型的变量,则它不会调用任何构造函数,因此所有成员都保持未分配状态。 因此,您必须在访问它们之前为每个成员赋值,否则会产生编译时错误。

struct Coordinate
{
    public int x;
    public int y;
}

Coordinate point;
Console.Write(point.x); // 编译错误

point.x = 10;
point.y = 20;
Console.Write(point.x); //输出: 10  
Console.Write(point.y); //输出: 20  

结构体的构造器

结构不能包含无参数构造函数。 它只能包含参数化构造函数或静态构造函数。

struct Coordinate
{
    public int x;
    public int y;

    public Coordinate(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

Coordinate point = new Coordinate(10, 20);

Console.WriteLine(point.x); //输出: 10  
Console.WriteLine(point.y); //输出: 20  

您必须在参数化构造函数中包含结构体的所有成员,并为它赋值; 否则,如果任何成员未配赋值的话,C# 编译器将给出编译时错误。

结构体的方法与属性

结构可以包含属性、自动实现的属性、方法等,与类相同。

struct Coordinate
{
    public int x { get; set; }
    public int y { get; set; }

    public void SetOrigin()
    {
        this.x = 0;
        this.y = 0;
    }
}

Coordinate point = Coordinate();
point.SetOrigin();

Console.WriteLine(point.x); //输出: 0  
Console.WriteLine(point.y); //输出: 0  

以下的结构体包含了静态方法

struct Coordinate
{
    public int x;
    public int y;

    public Coordinate(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public static Coordinate GetOrigin()
    {
        return new Coordinate();
    }
}

Coordinate point = Coordinate.GetOrigin();

Console.WriteLine(point.x); //输出: 0  
Console.WriteLine(point.y); //输出: 0 

ref struct

为了更高的性能 从 C# 7.2 开始,可以在结构类型的声明中使用 ref 修饰符。 ref 结构类型的实例只能堆栈上分配,并且不能转义到托管堆。 为了确保这一点,编译器将 ref 结构类型的使用限制如下:

  • ref 结构不能是数组的元素类型。
  • ref 结构不能是类或非 ref 结构的字段的声明类型。
  • ref 结构不能实现接口。
  • ref 结构不能被装箱为 System.ValueType 或 System.Object。
  • ref 结构不能是类型参数。
  • ref 结构变量不能由 lambda 表达式或本地函数捕获。
  • ref 结构变量不能在 async 方法中使用。 但是,可以在同步方法中使用 ref 结构变量,例如,在返回 Task 或 Task 的方法中。
  • ref 结构变量不能在迭代器中使用。

如下的示例,在struct没有加入ref之前是不会出错的 Coordinate也是会被分配到堆中

public ref struct Coordinate
{
    public int x;
    public int y;
}

public class Graph
{
    public Coordinate Coordinate { get; set; } //编译时报错 字段或自动实现的属性不能是类型“Coordinate”,除非它是 ref 结构的实例成员。
}

基于这个新的类库里多了 Span ReadOnlySpan, Memory 其它类也使用Span 来提高性能,减少内存占用 更详细的看 c-7-series-part-9-ref-structs

readonly struct

有时候当结构体创建之后,就不允许修改了。我们可以给这个struct加上readonly

public readonly struct Person
{
    public string Name { get; } //只能是get

    public string Surname { get; }

    public int Age { get; }

    public Person(string name, string surname, int age)
    {
        Name = name;
        Surname = surname;
        Age = age;
    }
}

Person s = new Person("asd", "qwe", 15); //只能用这个初始化的方式了

8.0之后可以对给成员添加readonly

public struct Coordinate
{
    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
    public readonly int X;
    public int Y;
}

public class Program
{
    public static void Main(string[] args)
    {
        var coordinate = new Coordinate(5, 8);
        coordinate.X = 100; //编译会出错
    }
}

结构体事件

结构体可以包含事件用来通知订阅者有关某些操作。 以下结构包含一个事件。

struct Coordinate
{
    private int _x, _y;

    public int x 
    {
        get{
            return _x;
        }

        set{
            _x = value;
            CoordinatesChanged(_x);
        }
    }

    public int y
    {
        get{
            return _y;
        }

        set{
            _y = value;
            CoordinatesChanged(_y);
        }
    }

    public event Action<int> CoordinatesChanged;
}

上述结构包含 CoordinatesChanged 事件,当 x 或 y 坐标发生变化时会引发该事件。 下面的示例演示了 CoordinatesChanged 事件的处理。

class Program
{
    static void Main(string[] args)
    {

        Coordinate point = new Coordinate();
        
        point.CoordinatesChanged += StructEventHandler;
        point.x = 10;
        point.y = 20;
    }

    static void StructEventHandler(int point)
    {
        Console.WriteLine("Coordinate changed to {0}", point);
    }
}

struct 是值类型,因此它比类对象更快。 只要您只想存储数据,比较小体量的数据,注生性能,就使用 struct 。 通常,struct适用于游戏编程, 量化交易。

总结

  • struct可以包括构造器、常数、字段、方法、属性、索引器、操作员、事件和嵌套类型。
  • struct不能包括无参数构造器或除损器。
  • struct可以实现接口,与类相同。
  • struct不能继承另一种结构或类,它不能是一个类的基础。
  • struct成员不能指定为抽象、密封、虚拟或受保护。

把数据转到byte[]

借助 Marshal可以方便把结构体数据映射到byte[]中。跟一些C++写的程序进行数据交换的时候很有用。

public static class StructHelper
    {
        static byte[] StructToBytes<T>(T structObj) where T : struct
        {
            int size = Marshal.SizeOf(structObj);
            IntPtr buffer = Marshal.AllocHGlobal(size);
            try
            {
                Marshal.StructureToPtr(structObj, buffer, false);
                byte[] bytes = new byte[size];
                Marshal.Copy(buffer, bytes, 0, size);
                return bytes;
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }

        }

        static object BytesToStruct<T>(byte[] bytes, Type strcutType)
        {
            int size = Marshal.SizeOf(strcutType);
            IntPtr buffer = Marshal.AllocHGlobal(size);
            try
            {
                Marshal.Copy(bytes, 0, buffer, size);
                return Marshal.PtrToStructure(buffer, strcutType);
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }
        }
    }
下一篇:C# 枚举 enum
最近更新的
...