◆ 値型で参照じゃなくて実体がコピーされる
◆ 値型のプロパティでは そのフィールドやプロパティを変更しても効果ない
  ◆ コンパイルエラーになる
◆ PHP の(連想)配列に近い

C# で久々に struct 使いました
久々と言ってもこれまで使ったことあるものは ライブラリや .NET Framework に用意された型ばかりで自分で作ったことはあったっけ?ってくらいです

基本 class でいいし

C# だと基本 class であまり struct の使い所がないんですよね
何バイトかより小さいサイズだと class より高速みたいな話を聞いたことありますが 大抵はプロパティが多数あってそんなに小さくならないですし
よくある Point 型みたいなシンプルなものくらいでしょうか

違い

最初の頃はデータ入れるのみでメソッドやプロパティはないものと思ってましたが class と同じようにメソッドもプロパティも普通に持てます
違うところは値型で代入や return で値がコピーされるということです
int も struct なので int 型みたいな動きをするわけですね

フィールドがあるとなんとなく参照型みたいに思いますが値型なので 別変数に代入してフィールドを変更しても 元の方には影響しません
フィールドも値型ならそれもコピーされますが class などの参照型なら参照がコピーされます
参照型のフィールドのフィールドを変えると両方に影響します

代入すると全体のコピーが発生するので サイズが小さいものでないと class より遅くなると言われるわけですね

他には引数なしのコンストラクタは作れません
デフォルトのコンストラクタが内部で用意されてるようで その構造体のデフォルトの値が作られます
デフォルトの値は null ではなく すべてのフィールドがデフォルト値のものです
? をつけた nullable な型にしないと null は許容されていません
デフォルトの値は default(type) で取得できるものです
int なら 0 ですし 何かの class なら null です

値型の問題

値型でコピーしてくれるのは便利なときもありますが 不便なところもあります
int みたいなものならいいのですが プロパティがある型ではけっこう困るやつです
これがあるので 基本 struct では自動プロパティ使うならフィールドで良い気がします

サンプルコードです
s は struct で c は class を意味してます
また f は field で p は property です
構造体とクラスのフィールドやプロパティに構造体やクラスを入れてそれらの値を変更します

using System;

namespace cs
{
class Program
{
static void Main(string[] args)
{
var s = new S()
{
f = 10,
p = 20,
cf = new Cc() { f = 1, p = 2 },
cp = new Cc() { f = 1, p = 2 },
sf = new Ss() { f = 1, p = 2 },
sp = new Ss() { f = 1, p = 2 },
};

var c = new C()
{
f = 10,
p = 20,
cf = new Cc() { f = 1, p = 2 },
cp = new Cc() { f = 1, p = 2 },
sf = new Ss() { f = 1, p = 2 },
sp = new Ss() { f = 1, p = 2 },
};

s.f = 100;
s.p = 100;
s.cf.f = 100;
s.cf.p = 100;
s.cp.f = 100;
s.cp.p = 100;
s.sf.f = 100;
s.sf.p = 100;
// s.sp.f = 100; // Err
// s.sp.p = 100; // Err

c.f = 100;
c.p = 100;
c.cf.f = 100;
c.cf.p = 100;
c.cp.f = 100;
c.cp.p = 100;
c.sf.f = 100;
c.sf.p = 100;
// c.sp.f = 100; // Err
// c.sp.p = 100; // Err
}
}

struct S
{
public int f;
public int p { get; set; }
public Cc cf;
public Cc cp { get; set; }
public Ss sf;
public Ss sp { get; set; }
}

struct Ss
{
public int f;
public int p { get; set; }
}

class C
{
public int f;
public int p { get; set; }
public Cc cf;
public Cc cp { get; set; }
public Ss sf;
public Ss sp { get; set; }
}

class Cc
{
public int f;
public int p { get; set; }
}
}

// でコメントアウトしてるところがコンパイルエラーになるところです
実行しても無駄なので わかりやすいようにコンパイル時のエラーにしてくれます

プロパティは {get;set;} の自動生成に任せると分かりづらいですが 中身は関数の実行です
s.sp や c.sp で構造体のプロパティを参照するところでは getter の関数が実行されてバッキングフィールドの値が返されます
関数を通して return されてるわけですから フィールドの値を直接参照してるのではなくコピーを参照しています
変数などに保存していない その場限りのコピーされた構造体のフィールドやプロパティを書き換えることになるので 意味のない処理です
setter で外部のグローバルな値を変更したり Console.WriteLine してれば完全に無意味ではないですが 本来の setter の仕事としては更新したものがすぐに GC で消えるだけなので意味がないことになります

実行後は コメントアウトしているところ以外の各フィールドとプロパティの値は 100 になってます

便利な場合

比較やコピーをするのは便利です

A が構造体で B がクラスでどちらも int 型のフィールドを持ってます
through メソッドは入力をそのまま返す関数です
構造体の場合はコピーしてることになります

using System;

namespace cs
{
class Program
{
static void Main(string[] args)
{
var a = new A() { a = 10 };
var a2 = through(a);

System.Console.WriteLine(a.Equals(a2));
a.a = 100;
System.Console.WriteLine(a.a);
System.Console.WriteLine(a2.a);
System.Console.WriteLine(a.Equals(a2));

var b = new B() { b = 10 };
var b2 = through(b);

System.Console.WriteLine(b.Equals(b2));
b.b = 100;
System.Console.WriteLine(b.b);
System.Console.WriteLine(b2.b);
System.Console.WriteLine(b.Equals(b2));
}

public static T through<T>(T k)
{
return k;
}
}

struct A
{
public int a;
}

class B
{
public int b;
}
}

a の方では コピーしたばかりの a2 とは同じ値です
a.a を更新すると a.a と a2.a は別の値になり 比較は false です

b の方では 参照が同じなのでフィールドの変更は b と b2 両方に影響します

コピーを作りたいときや 同じ値かを比較したいときがありますが クラスなど面倒なことが多いです
その点では構造体のほうが便利です
ただ コピーや比較をしやすいがために全部を構造体にするのもどうかと思うので このメリットはあまり生かせない気はします

PHP の配列

C# の構造体に近いのが PHP の(連想)配列です
文字列のキーに対する値を持てるので フィールドに似ています

PHP の配列は値型で代入でコピーされます
ということは C# の構造体の問題は PHP でも起きるはずです

クラスが構造体をフィールドに持っているというのを PHP で再現してみました
クラス A のインスタンス $a の x と y のプロパティに配列を設定して その値を書き換えます
x は普通のプロパティで C# のフィールドに相当し y は __get と __set を使って C# のプロパティに相当するようしています

<?php

class A {
public $x;
private $_data = [];

public function __get($name){
return @$this->_data[$name];
}

public function __set($name, $value){
$this->_data[$name] = $value;
}
}

$a = new A();
$a->x = ["a" => 10];
$a->y = ["a" => 10];

$a->x["a"] = 100;
$a->y["a"] = 100;

var_dump($a->x, $a->y);

PHP Notice:  Indirect modification of overloaded property A::$y has no effect in /mnt/c/users/u32/desktop/0.php on line 21
array(1) {
["a"]=>
int(100)
}
array(1) {
["a"]=>
int(10)
}

PHP でも 同じようにコピーが返される y では変更が反映されてません
C# のようなコンパイルエラーはないですが 代わりに実行時に Notice で A::$y には影響ないよ と教えてくれてます