[C#] 18. 構造体(Struct)、そして値型を参照するタイプ(Reference of value type)と参照型を参照するタイプ(Reference of reference type)


Study / C#    作成日付 : 2019/07/10 23:57:25   修正日付 : 2021/08/31 18:20:54

こんにちは。明月です。


この投稿はC#で構造体(Struct)、そして値型を参照するタイプ(Reference of value type)と参照型を参照するタイプ(Reference of reference type)に関する説明です。


以前の投稿でクラスとインスタンス生成に関して説明したことがあります。

link - [C#] 10. クラスを作成する方法(コンストラクタ、デストラクタ)

link - [C#] 11. インスタンスう生成(new)とメモリ割り当て(StackメモリとHeapメモリ)そしてヌル(null)


基本的にC#で構造体というのはクラスと似ています。

using System;

namespace Example
{
  // 構造体生成
  struct Example
  {
    // メンバー変数
    private int data;
    // メンバー変数設定関数
    public void SetData(int data)
    {
      // メンバー変数に値を設定
      this.data = data;
    }
    // 出力関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 実行関数
    public static void Main(string[] args)
    {
      // インスタンス生成
      Example ex = new Example();
      // 値設定
      ex.SetData(10);
      // 関数実行
      ex.Print();
 
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例をみればクラスを作成することと構造体を作成することは同じですね。

クラスと構造体が何の差異があるかとみれば値型を参照するタイプ(Reference of value type)と参照型を参照するタイプ(Reference of reference type)があります。

つまり、クラスの場合はHeapメモリにクラスのインスタンス生成してStackメモリに変数を設定してインスタンスのポインタを格納します。

そうしたら、他の変数にイコール(=)を使ってポインタ値を格納するかパラメータでポインタを渡した後、インスタンスの値を変更したら、他に連携したインスタンスにも影響になります。

using System;

namespace Example
{
  // クラス生成
  class Example
  {
    // メンバー変数
    private int data;
    // メンバー変数に設定する関数
    public void SetData(int data)
    {
      // メンバー変数に値を設定
      this.data = data;
    }
    // 出力関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 実行関数
    public static void Main(string[] args)
    {
      // インスタンス生成
      Example ex = new Example();
      // 値設定
      ex.SetData(10);
      // exのインスタンスのポインタをex1に格納
      Example ex1 = ex;
      // ex1の関数にメンバー変数の値を設定
      ex1.SetData(20);
      // exのインスタンスの関数で出力すれば値が変更されている。
      ex.Print();
 
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の結果をみればexの変数のdataの値が変わっています。

つまり、クラスの参照するタイプ(Reference of reference type)は下記とおりに連携しています。

クラスの連携構造は以前に説明したことがあります。


でも構造体がクラスと違いいます。

using System;

namespace Example
{
  // 構造体生成
  struct Example
  {
    // メンバー変数
    private int data;
    // メンバー変数に設定する関数
    public void SetData(int data)
    {
      // メンバー変数に値を設定
      this.data = data;
    }
    // 出力関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 実行関数
    public static void Main(string[] args)
    {
      // インスタンス生成
      Example ex = new Example();
      // 値設定
      ex.SetData(10);
      // exのインスタンスをex1に格納(ポインタコピーではなく、インスタンスがコピーされる)
      Example ex1 = ex;
      // ex1の関数にメンバー変数の値を設定
      ex1.SetData(20);
      // exを出よくすればex1の影響がないので、値が変更されてない。
      ex.Print();
 
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上のソースは以前のExampleクラス(class)で構造体(struct)に変更したことしかありません。

でも、結果は違います。

構造体の連携図をみれば下記通りになります。


構造体はイコール(=)でポインタコピーではなく、インスタンスがコピーされています。

これが値型を参照するタイプ(Reference of value type)と言います。

つまり、我々が原始データを扱う時、つまり、int a=1;int b=a;ということに設定する場合、変数のbを設定したら変数のaの値は変わらないです。これが値型を参照するタイプ(Reference of value type)です。


でも、この構造体(struct)を関数のパラメータにデータを渡す時、値型を参照するタイプ(Reference of value type)ではなく、参照するタイプ(Reference of reference type)に渡したい時があります。

その時にrefキーワードを使って参照するタイプ(Reference of reference type)に変更ができます。

using System;

namespace Example
{
  // 構造体生成
  struct Example
  {
    // メンバー変数
    private int data;
    // メンバー変数に設定する関数
    public void SetData(int data)
    {
      // メンバー変数に値を設定
      this.data = data;
    }
    // 出力関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // refキーワードを使ってパラメータから値型を参照するタイプではなく、参照するタイプに変更が可能。
    static void SetExampleData(ref Example p1)
    {
      p1.SetData(20);
    }
    // 実行関数
    public static void Main(string[] args)
    {
      // インスタンス生成
      Example ex = new Example();
      // 値設定
      ex.SetData(10);
      SetExampleData(ref ex);
      // exを出力すれば値が変更されている。
      ex.Print();
 
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


当たり前の話ですが、refキーワードを使わないと、構造体は値型を参照するタイプでデータ値が移動するので、exの値が変更されていません。

クラスには基本に参照型を参照するタイプなので要りません。


構造体はクラスと違い特性があります。

まず、パラメータがないコンストラクタは作成できません。


メンバー変数をpublicで外部から参照することに設定すると思えば、newキーワードを使わなくて、インスタンス生成ができます。

using System;

namespace Example
{
  // 構造体生成
  struct Example
  {
    // メンバー変数
    public int data;
    // 出力関数
    public void Print()
    {
      // コンソール出力
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 実行関数
    public static void Main(string[] args)
    {
      // newキーワード無し。
      Example ex;
      // メンバー変数設定
      ex.data = 10;
      // exの関数が呼び出しができる。
      ex.Print();
 
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


なので、構造体はヌル(null)が存在しません。


構造体は基本構造が値型を参照するタイプなので継承が存在しません。


個人的な考えですが、この構造体は以前のC++言語の跡ではないかと思います。C++には構造体を使ったらバイナリを構造体タイプに読み込むことができます。

でも、その機能がC#ではできません。なので、C#で構造体をあまり使わないですね。

なぜなら、クラスと使う方法も似ているし、カプセルとクラスの値型を参照するタイプ(Reference of value type)と参照型を参照するタイプ(Reference of reference type)の差異があるので、混乱するし読みやすさ(可読性)が悪くなる欠点があります。


ここまでC#で構造体(Struct)、そして値型を参照するタイプ(Reference of value type)と参照型を参照するタイプ(Reference of reference type)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿