読者です 読者をやめる 読者になる 読者になる

構造体

C#では、クラスは参照型、構造体は値型という大きな違いがある・・・。

TestClass.csを、

// 名前空間の登録
using System;       // 共通データ型と基本クラス(System名前空間)

// 文字列を持つクラス
class TestClass // TestClassクラスの定義
{
    // メンバフィールドの定義
    private string str; // string型フィールドstr
    public string Str   // strの設定と取得をするプロパティStr.
    {
        get
        {
            // strを返す.
            return str;
        }
        set
        {
            // strにvalueを格納.
            str = value;
        }
    }
    private int i;  // int型フィールドi
    public int I    // iの設定と取得をするプロパティI.
    {
        get
        {
            // iを返す.
            return i;
        }
        set
        {
            // iにvalueを格納.
            i = value;
        }
    }
    // iとstrを表示.
    public void Show()
    {
        Console.WriteLine("i = " + i + ", str = " + str);
    }
}

TestStruct.csを、

// 名前空間の登録
using System;       // 共通データ型と基本クラス(System名前空間)

// 文字列を持つ構造体
struct TestStruct // TestStruct構造体の定義
{
    // メンバフィールドの定義
    private string str; // string型フィールドstr
    public string Str   // strの設定と取得をするプロパティStr.
    {
        get
        {
            // strを返す.
            return str;
        }
        set
        {
            // strにvalueを格納.
            str = value;
        }
    }
    private int i;  // int型フィールドi
    public int I    // iの設定と取得をするプロパティI.
    {
        get
        {
            // iを返す.
            return i;
        }
        set
        {
            // iにvalueを格納.
            i = value;
        }
    }
    // iとstrを表示.
    public void Show()
    {
        Console.WriteLine("i = " + i + ", str = " + str);
    }
}

としておく・・・。

そして、MainClass.csでいろいろ試してみる・・・。

// 名前空間の登録
using System;       // 共通データ型と基本クラス(System名前空間)

// メインクラス
class MainClass // MainClassクラスの定義
{

    // メインメソッド
    static void Main()  // Mainメソッドの定義
    {

        // オブジェクトの宣言
        TestClass testClass;    // TestClassオブジェクトtestClassの宣言
        TestStruct testStruct;  // TestStructオブジェクトtestStructの宣言

        // nullかどうかを調べる.
        if (testClass == null)  // testClassがnullなら.
        {
            Console.WriteLine("testClass == null!");    // "testClass == null!"と出力.
        }
        if (testStruct == null) // testStructがnullなら.
        {
            Console.WriteLine("testStruct == null!");    // "testStruct == null!"と出力.
        }

    }

}

まず、こうしてみたら、

------ ビルド開始: プロジェクト: struct, 構成: Debug Any CPU ------
C:\Windows\Microsoft.NET\Framework\v2.0.50727\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /debug+ /debug:full /optimize- /out:obj\Debug\struct.exe /target:exe MainClass.cs TestClass.cs TestStruct.cs
C:\Project\Cloud\github.com\Sample\cs\struct\struct\src\struct\struct\MainClass.cs(21,13): エラー CS0019: 演算子 '==' を 'TestStruct' と '<null>' 型のオペランドに適用することはできません。

コンパイルの完了 -- エラー 1、警告 0
========== ビルド: 0 正常終了または最新の状態、1 失敗、0 スキップ ==========

と出た・・・。testStructは値型なのでnullとは比較できないようだ・・・。

testStruct関係はいったんコメントアウトして、

        // オブジェクトの宣言
        TestClass testClass;    // TestClassオブジェクトtestClassの宣言
        TestStruct testStruct;  // TestStructオブジェクトtestStructの宣言

        // nullかどうかを調べる.
        if (testClass == null)  // testClassがnullなら.
        {

としてみたら、

C:\Project\Cloud\github.com\Sample\cs\struct\struct\src\struct\struct\MainClass.cs(17,13): エラー CS0165: 未割り当てのローカル変数 'testClass' が使用されました。

未割当だとnull比較さえ、ダメみたいだ・・・。
(C++だとこういうのはせいぜい警告で済むのだが厳しい・・・。)

        // オブジェクトの宣言
        TestClass testClass;    // TestClassオブジェクトtestClassの宣言
        TestStruct testStruct;  // TestStructオブジェクトtestStructの宣言

        // testClassのインスタンスを生成.
        testClass = new TestClass();    // testClassは参照型であり, 初期状態はnullなので, newでインスタンス生成しないと, メ

おとなしくnewでインスタンス生成・・・。

        // インスタンスの生成
        testClass = new TestClass();    // TestClassのインスタンスをtestClassに格納.(実際には参照している.)
        
        // 値の代入.
        testClass.I = 10;       // testClass.Iに10を代入.(プロパティ経由可)
        testClass.Str = "A";    // testClass.Strに"A"を代入.(プロパティ経由可)
        testClass.Show();       // testClass.Showでiとstrを表示.(メソッドも呼べる.)

プロパティもメソッドも呼べる・・・。

さて、構造体だとどうだろうか・・・。
さっきのnull判定はしょうがないとして、

        // オブジェクトの宣言
        TestClass testClass;    // TestClassオブジェクトtestClassの宣言
        TestStruct testStruct;  // TestStructオブジェクトtestStructの宣言

        // インスタンスの生成
        testClass = new TestClass();    // TestClassのインスタンスをtestClassに格納.(実際には参照している.)

        // 値型の値をプロパティを経由して入れてみる.
        testStruct.I = 100; // testStruct.Iに100を代入.

構造体は宣言時にメモリが割り当てられているらしいので、プロパティ経由で値を入れようとしたが、

C:\Project\Cloud\github.com\Sample\cs\struct\struct\src\struct\struct\MainClass.cs(20,9): エラー CS0165: 未割り当てのローカル変数 'testStruct' が使用されました。

割り当てられているなら「newはいらないのでは?」とおもった・・・。

そこで、TestStruct.csで、

や、

というように、privateメンバフィールドをpublicにして、

        // 値を直接入れてみる.
        testStruct.i = 100; // testStruct.iに100を代入.

メンバフィールドに直接入れてみる・・・。
これだと、

コンパイルの完了 -- エラー 0、警告 0

通ってしまう・・・。

        // 文字列に直接の場合.
        testStruct.str = "ABC"; // testStruct.strに"ABC"を格納.

これも通る・・・。
しかし、プロパティ経由だと通らない・・・。

そして、

        testStruct.Show();

メソッドでも通らなかった・・・。
どうも、アクセスできるのはメンバフィールドだけの模様・・・。

実は構造体にもnewがある・・・。

testStruct = new TestStruct();  // testStructとnew TestStructは別物で中身がコピーされる.

コメントにもあるようにこれは、TestStructオブジェクトを生成し、その参照をtestStructに渡しているわけではなく、そのオブジェクトごとtestStructにコピーしているのである・・・。
なので、まあnew TestStructは無駄になるのだが・・・。
しかし、こうすると、

        // 構造体のnewは生成インスタンスのコピー.
        testStruct = new TestStruct();  // testStructとnew TestStructは別物で中身がコピーされる.
        testStruct.I = 100; // 中身がコピーされた状況だとプロパティ経由可.
        testStruct.Str = "ABC"; // 中身がコピーされた状況だとプロパティ経由可.
        testStruct.Show();  // testStruct.Showでiとstrを出力.

これでも、

コンパイルの完了 -- エラー 0、警告 0

通る・・・。
(ふしぎー・・・。)

やはり、構造体はnewで生成してコンストラクタなどで値を初期化すべきだなあ・・・。

さて、クラスは参照型、構造体は値型、ということであれば、クラスオブジェクトは参照渡し、構造体オブジェクトはコピーになることも確認しないと・・・。

最終的にMainClass.csは、

として、ClassStructMethodにtestClassオブジェクトと、testStructオブジェクトを渡して、内部でメンバを変更したらそれが反映されるかを試してみる・・・。

i = 10, str = A
i = 100, str = ABC
i = 20, str = X
i = 100, str = ABC
続行するには何かキーを押してください . . .

最終的な結果はこうなる・・・。
testClassは参照型なので、メンバの値の変更が反映されてる・・・。
一方、testStructはコピーで渡されるので、メンバの値の変更は元のオブジェクトに影響しない・・・。

※.訂正
プロパティとメソッドが呼べない件は、
構造体 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
全てのフィールドの初期化までは呼べない制約で、生成とかの話ではない模様・・・。

Sample/cs/struct/struct/src/struct at master · bg1bgst333/Sample · GitHub

構造体

C++にも構造体があるが、クラスとの違いで代表的なのは、クラスのデフォルトのメンバアクセスがprivateなのに対し、構造体のデフォルトのメンバアクセスがpublicであること・・・。

struct_test.hで、

アクセス指定子を付けずにメンバを用意・・・。
(C++では、構造体でもメンバ関数を使えるし、構造体タグが不要なところがC言語と違う・・・。)

struct_test.cppは、

こんな感じ・・・、

class_test.hは、

// クラスclass_testの定義.
class class_test{ // テストクラス

  // アクセス指定子を付けずにメンバを宣言.
  int num; // メンバ変数num.
  void show(); // メンバ関数show.

};

class_test.cppは、

で、main.cppは、

しかし、コンパイルすると、

$ g++ -o main main.cpp struct_test.cpp class_test.cpp
In file included from main.cpp:6:0:
class_test.h: 関数 ‘int main()’ 内:
class_test.h:5:7: エラー: ‘int class_test::num’ は非公開です
   int num; // メンバ変数num.
       ^
main.cpp:20:6: エラー: within this context
   ct.num = 20; // ct.numに20を代入.
      ^
In file included from main.cpp:6:0:
class_test.h:6:8: エラー: ‘void class_test::show()’ は非公開です
   void show(); // メンバ関数show.
        ^
main.cpp:21:11: エラー: within this context
   ct.show(); // ct.showで出力.
           ^
$

class_testのメンバが非公開なのでアクセスできない・・・。
一方、struct_testはエラーが出ない・・・。もともとデフォルトで公開なので問題ない・・・。

class_test.hに、

publicをつければ、コンパイル出来て、

$ g++ -o main main.cpp struct_test.cpp class_test.cpp
$ ./main
struct_test.num = 10
class_test.num = 20
$

こうなる・・・。

Sample/cpp/struct/struct/src/struct at master · bg1bgst333/Sample · GitHub

Object

これまでも出てきたobject型は、その正体はObjectクラスのエイリアスであり、すべての.NET Frameworkクラスの基底となるクラスである・・・。

Object クラス (System)

C#に出てくる型は、実体はCTSで定義されたクラスや構造体のエイリアス・・・。

CTS 型

Objectのインスタンスと、objectのインスタンスが同じかを確認・・・。

GetTypeで型の種類を取得し、ToStringで文字列化して、出力・・・。

objType: System.Object
objClass: System.Object
続行するには何かキーを押してください . . .

確かにobjectはObjectだった・・・。

Sample/dotnet/Object/Object/src/Object_ at master · bg1bgst333/Sample · GitHub

新しいデリゲート

C# 2.0から、新しいデリゲートの書き方が可能になり、メソッド名をそのまま左辺に持ってくることが可能になった・・・。

<デリゲート型名> <デリゲートオブジェクト> = <呼び出すメソッド名>;

これまで、

// 名前空間の登録
using System;       // 共通データ型と基本クラス(System名前空間)

// デリゲートの定義.
delegate int AddDelegate(int a, int b); // 引数がint型のa, b, 戻り値がint型のメソッドを持つデリゲートAddDelegate.

// メインクラス
class MainClass // MainClassクラスの定義
{

    // メインメソッド
    static void Main()  // Mainメソッドの定義
    {

        // デリゲートの生成.
        AddDelegate add = new AddDelegate(Add); // Addを呼ぶAddDelegate型addを生成.

        // addが持つメソッドを実行.
        int result = add(10, 20);    // addに10と20を渡し, 結果をresultに格納.

        // 結果の出力.
        Console.WriteLine("result = " + result);    // resulの値を出力.

    }

    // aとbを足すメソッド.
    static int Add(int a, int b)
    {

        // aとbを足した値を返す.
        return a + b;   // a + bを返す.

    }

}

こうだったのが、

この書き方でもよくなった・・・。

結果は、

result = 30
続行するには何かキーを押してください . . .

変わらない・・・。

newでわざわざデリゲートオブジェクトを作るところは確かに冗長だったかもしれない・・・。

Sample/cs/NewDelegate/NewDelegate/src/NewDelegate at master · bg1bgst333/Sample · GitHub

匿名メソッド

これまで、デリゲートオブジェクトにメソッドを格納する形で、さまざまな場所でそのメソッドを呼ぶことができた・・・。
しかし、メソッド自体は、デリゲートの条件に合う引数型と戻り値型で名前の付いたメソッドをきちんと定義しないといけなかった・・・。

名前の無い即興的なメソッドを定義し、それをデリゲートに格納できるのが匿名メソッド・・・。

引数として渡された2つのint型整数の足した値を返す関数・・・。
それを格納するデリゲートを普通のデリゲートで定義すると、

// 名前空間の登録
using System;       // 共通データ型と基本クラス(System名前空間)

// デリゲートの定義.
delegate int AddDelegate(int a, int b); // 引数がint型のa, b, 戻り値がint型のメソッドを持つデリゲートAddDelegate.

// メインクラス
class MainClass // MainClassクラスの定義
{

    // メインメソッド
    static void Main()  // Mainメソッドの定義
    {

        // デリゲートの生成.
        AddDelegate add = new AddDelegate(Add); // Addを呼ぶAddDelegate型addを生成.

        // addが持つメソッドを実行.
        int result = add(10, 20);    // addに10と20を渡し, 結果をresultに格納.

        // 結果の出力.
        Console.WriteLine("result = " + result);    // resulの値を出力.

    }

    // aとbを足すメソッド.
    static int Add(int a, int b)
    {

        // aとbを足した値を返す.
        return a + b;   // a + bを返す.

    }

}

しかし、匿名メソッドを使うと、

<デリゲート型> <デリゲートオブジェクト> = delegate(<引数型> <引数名>, ...){

    <匿名メソッドで行う処理>

}

というような形になるので、

こんな感じ・・・。
実行の仕方は変わらない・・・。
そして、メソッドAddがなくなったのでちょっとすっきり・・・。

Sample/cs/AnonymousMethod/AnonymousMethod/src/AnonymousMethod at master · bg1bgst333/Sample · GitHub

Control.BeginInvoke

ButtonやTextBoxなどの基底クラスとなるControlクラスにも、BeginInvokeはある・・・。

Control.BeginInvoke メソッド (Delegate) (System.Windows.Forms)

これも非同期処理に関するものだが、C#コンパイラの用意するBeginInvokeとはちょっと違い、非同期処理を実行するワーカースレッドからコントロールの操作を行う時に使う・・・。

f:id:BG1:20170328073727p:plain

今回はWindowsアプリケーション・・・。

f:id:BG1:20170328073824p:plain

Buttonを、

f:id:BG1:20170328073917p:plain

大きく配置・・・。

f:id:BG1:20170328073942p:plain

フォントも24と大きく・・・。

f:id:BG1:20170328074050p:plain

テキストも、

f:id:BG1:20170328074234p:plain

"開始"に変更・・・。

button1のClickに対するハンドラは、

        // button1がクリックされた時.
        private void button1_Click(object sender, EventArgs e)
        {

            // "開始"から"実行中..."へ.
            button1.Text = "実行中...";    // button1.Textに"実行中..."をセット.

            // 10秒待つ.(擬似的な重たい処理.)
            Thread.Sleep(10000);    // Thread.Sleepで10秒休止.

            // "実行中..."から"開始"に.
            button1.Text = "開始";  // button1.Textに"開始"をセット.

        }

最初、こうしてみる・・・。
Thread.Sleepで10秒かかる疑似的に重たい処理、前後でテキストを変化させる・・・。

f:id:BG1:20170328110131p:plain

このように、重たい処理をUIスレッド上で行うと、テキストは変化しないし、固まってしまう・・・。

前回までにやったBeginInvokeやAsyncCallbackを使って、重たい処理を非同期化する・・・。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.Remoting.Messaging;    // AsyncResultクラス(System.Runtime.Remoting.Messaging名前空間)
using System.Text;
using System.Threading; // マルチスレッド(System.Threading名前空間)
using System.Windows.Forms;

// デリゲートの定義
delegate void AsyncButtonDelegate(); // 引数も戻り地も無い関数を持つデリゲートAsyncButtonDelegate.

namespace Control_
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // button1がクリックされた時.
        private void button1_Click(object sender, EventArgs e)
        {

            // 非同期処理で実行するデリゲートasyncButtonの生成.
            AsyncButtonDelegate asyncButton = new AsyncButtonDelegate(AsyncButton); // AsyncButtonを呼ぶAsyncButtonDelegateオブジェクトasyncButtonの生成.

            // 非同期処理の完了後に実行するAsyncCallbackデリゲートcallbackの生成.
            AsyncCallback callback = new AsyncCallback(AsyncCallbackFunc);  // 非同期処理完了後にAsyncCallbackFuncを実行するAsyncCallbackオブジェクトcallbackを生成.

            // 非同期処理の開始.
            asyncButton.BeginInvoke(callback, null);    // asyncButton.BeginInvokeで非同期処理開始(非同期処理完了後に実行するcallbackを渡している.)

        }

        // 非同期処理ボタンメソッドAsyncButton.
        public void AsyncButton()
        {

            // "開始"から"実行中..."へ.
            button1.Text = "実行中...";    // button1.Textに"実行中..."をセット.

            // 10秒待つ.(擬似的な重たい処理.)
            Thread.Sleep(10000);    // Thread.Sleepで10秒休止.

            // "実行中..."から"終了"に.
            button1.Text = "終了";  // button1.Textに"終了"をセット.

        }

        // 非同期処理完了後のコールバックメソッドAsyncCallbackFunc.
        public void AsyncCallbackFunc(IAsyncResult ar)
        {

            // IAsyncResult型arをAsyncResultにキャスト.
            AsyncResult asyncRes = (AsyncResult)ar; // arをAsyncResult型asyncResにキャスト.

            // 非同期処理で使ったデリゲートを取得.
            AsyncButtonDelegate asyncButton = (AsyncButtonDelegate)asyncRes.AsyncDelegate;   // asyncRes.AsyncDelegateをキャストしてasyncButtonを取得.

            // 非同期処理の完了.
            asyncButton.EndInvoke(ar); // asyncButton.EndInvokeで完了.

            // 3秒待つ.(元に戻す処理.)
            Thread.Sleep(3000);    // Thread.Sleepで3秒休止.

            // "終了"から"開始"に.
            button1.Text = "開始";  // button1.Textに"開始"をセット.

        }
    }
}

button1がクリックされたら、asyncButton.BeginInvokeでAsyncButtonが実行され、それが終ったら渡したcallbackが持つAsyncCallbackFuncが呼ばれる・・・。

しかし、これを実行すると、

f:id:BG1:20170328111356p:plain

このような例外が出てしまう・・・。
テキストを変更するなどのコントロールを操作する処理はUIスレッドで行わないといけないからである・・・。
(AsyncCallbackFuncはUIスレッドだと思っていたが、どうもこれも例外が出たのでワーカースレッドらしい・・・。)

そこでControl.BeginInvokeである・・・。
引数に渡したデリゲートが持つメソッドがそのControlのスレッド(UIスレッド)で実行されるのである・・・。

AsyncButtonの中で、button1.BeginInvokeにrunningを渡すと、Runningが実行されて"実行中..."になり、finishを渡すと、Finishが実行されて"終了"になり、AsyncCallbackFuncの中ではあるが、startを渡すとStartが実行されて"開始"になる・・・。
(今回は引数なしバージョンを使ったので、いっぱいデリゲート作ってしまったが、引数ありバージョンなら表示するテキストを渡すだけの1つのデリゲートで済む・・・。)

f:id:BG1:20170328122438p:plain

押すと、

f:id:BG1:20170328122529p:plain

"実行中..."になり、しばらく待つと、

f:id:BG1:20170328122608p:plain

"終了"となり、ちょっと待つと、

f:id:BG1:20170328122651p:plain

"開始"に戻る・・・。

Sample/dotnet/Control/BeginInvoke/src/Control_ at master · bg1bgst333/Sample · GitHub

AsyncResult

これまでは、BeginInvokeで非同期処理を実行してから、EndInvokeで非同期処理が終わるのを待っていた・・・。
しかし、これだと同期的に処理している事と変わらない・・・。
BeginInvokeで非同期処理を実行した後も、Mainは先の処理を進めつつ、一方で非同期処理完了後のコールバックで終了を通知する形が本来の非同期処理と言えるだろう・・・。

コールバックメソッドの引数IAsyncResultをAsyncResultにキャストすると、IAsyncResultではアクセスできないプロパティを取得できる・・・。

AsyncResult クラス (System.Runtime.Remoting.Messaging)

その中には非同期処理に使ったデリゲートもあり、EndInvokeで結果の値を取得出来たりする・・・。

Main側でEndInvokeを呼ばず、コールバック側でEndInvokeを呼んで結果を取得する・・・。

int型a, bを引数に取り、戻り値はintになるメソッドのデリゲートAsyncronousFuncDelegate・・・。

asyncronousFunc、callback、を生成して、asyncronousFunc.BeginInvokeには、10、20とcallbackなど指定・・・。
"AsyncronousFunc..."と出力したら終わりなのだが、コンソールだとMainが終わってしまうと非同期処理側も実行されないので、Console.ReadKeyで入力されるまで待つ・・・。
(結局、実質的には同期的になっている・・・。Windowsアプリケーションなら終わらないで待てるが・・・。)

非同期処理AsyncronousFuncは、3秒休止したら引数のaとbの足した結果を返す・・・。

非同期処理完了後のコールバックでは、引数のarをIAsyncResultからAsyncResultにキャストしてasyncResに・・・。
asyncRes.AsyncDelegateで非同期処理に使ったasyncronousFuncを取得できる・・・。(objectで格納されてるのでAsyncronousFuncDelegateにキャストする必要あり・・・。)
これでasyncronousFunc.EndInvokeでAsyncronousFuncの戻り値resultを取得できる・・・。

AsyncronousFunc...
result = 30
続行するには何かキーを押してください . . .

コンソールだからMain側は結局待ったが、Windowsアプリケーションなら、Main側が終了してから随分あとに通知することもできる・・・。

Sample/dotnet/AsyncResult/AsyncResult/src/AsyncResult_ at master · bg1bgst333/Sample · GitHub