sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

ふるさと納税に使える金額を自分で頑張って調べる方法

社会人の方だとわりとやってるだろう「ふるさと納税」について、個人的なやり方・調べたことをメモ。

いわゆる、ふるさと納税に使える上限金額、特例分の上限金額をどうやって調べるのか、です。いろんなシミュレーターがあるのですが、中でどんな計算をしてるのか信用しきれず、自分なりに調べてみた記録です。

ふるさと納税に使った金額の宛先

つぎの3つに充てられます。

  • 所得税からの控除(総所得の 40% まで)
  • 住民税からの控除(基本)(総所得の 30% まで)
  • 住民税からの控除(特例)(総所得の 20% まで)

和光市の書き方がわかりやすかったです。

ざっくりと来年支払う税金から控除を受けるという仕組みのようです。このうち、一番上限が低い「住民税からの控除(特例)」の上限を「ふるさと納税に使える上限金額」とするようですね。

ちなみに、住民税は、市民税4%と都・県・府民税6%をまとめたもので、およそ10%ですが、細かくは微妙に違います

ともかく、今年なら 2020 年にいくら課税の対象となる収入があったかがポイントになります。まず、これがどんな感じなのか調べてみます。課税対象となる所得を「課税標準の総所得」というみたいです。これは、毎年6月ごろに市民税・府民税の徴収通知書が届いているので、それを見ればわかります。

ふるさと納税をして控除できた金額を知るのもそのタイミングになるはずです。なので、2020 年の分は 2021 年の6月の通知書でわかるはずです。

課税標準の総所得の計算

徴収通知書に書かれている数字(金額)を整理しながら進めていきます。徴収通知書のサンプルはこちら。

f:id:shikaku_sh:20201127173112j:plain:w600
参考になりそうなあたりに目印を入れました

給与所得の計算

まず、課税対象になる所得「課税標準の総所得」をどのように計算しているのかやってみました。まず、完全な収入金額「給与収入」がスタート地点になります。この次が「給与所得」です。ここで結構な金額が減っていますが、これは給与所得控除額を除いた金額です。

給与所得控除額とは、いわゆる「経費」です。なので、課税対象外のお金として扱ってくれているので、「給与所得」の額面が小さくなっているなら、その分だけ課税対象となる収入が少ないということです。お得感あります。

この給与所得控除額も 2020 年はポイントになっています。2019 年のときよりも、ざっくりと10万円少なくなりました。なので、2020 年は給与所得が10万円高くなるはずです。その分だけ課税対象となる収入になる、ということです。ここのロジックがそれぞれの名詞の意味を理解していないと頭がバグりそうですね。

計算式はこちら国税庁の式を参照してください。

たとえば、360 万円~660 万円の給与収入だとすると、給与所得控除額は以下の式で計算できるようです。

{
収入金額×20%+440,000円
}


余談ですが、この式で電卓計算しても通知書の金額とぴったり一致しませんでした。(数千円ズレます)しかし、国税庁の給与所得(給与収入 - 給与所得控除額)を計算する Java Script で計算するとぴったり一致します。コードを見てみるとこんな式になっています。

kakaku = 給与収入の金額;
m = kakaku;

m = (parseInt(m/4000))*4000;
z=m*80/100-440000;

alert("給与所得の金額は、" + "(" + change(kakaku) + "÷4 (千円未満の端数切捨て))×4×80%-440,000=" + change(z) + "円になります。");

4000 で割って整数にして、4000 で掛け算して、元の金額の千円以下の値を消し込んでいますね。これは誤差の原因になりそうですね。しかし、これだと端数を切るだけ(1000 で割って掛け直せよって)なりませんか? 小数点以下の端数が変化しちゃうから、これも誤差の原因になっています。

調べてみたところ、実際に正となるのは所得税法別表第五のようになります。この別表が曲者で、4,000 円区切りで給与所得控除額が決まっているのですよね。なので、4000 で割り算して、小数点以下のマス目を別表にあわせたんだと思います。(私しらべ)

給与所得者の基礎控除申告書 だと、それをフォローする式になっています。 4 で割ってから 3.2 を掛けています。(80% × 4 = 3.2)

そんな感じで、給与所得が算出できました。

所得控除

所得控除は、給与所得からさらに控除される金額で、課税対象となる総所得=(給与所得 - 所得控除)で決定しています。(所得控除合計も千円以下は切捨みたいです)

なので、これも課税対象の金額に含まれないので、家計簿的には助かるけど、ふるさと納税の金額は減る要素になります。(保険、扶養、配偶者等の控除はここで計算)

ちなみに社会保険料は給与収入に対して 15% ~ 14.22% あたりになることが多いため、簡易シミュレーターはそんな感じで動作しているようです。

ともかく、これで「課税標準の総所得」を計算できました。

所得税

課税標準の総所得」から所得税率がわかります。

課税対象額 税率
195万円まで 5%
330万円まで 10%
695万円まで 20%
900万円まで 23%
1800万円まで 33%
4000万円まで 40%
4000万円以上 45%

※実際の所得税率は令和19年まで復興特別所得税として所得税率×1.021%されたものとなります。

たとえば、400~500万円くらい稼いでいる人だと 5% か 10% のどちらになるかで、ふるさと納税金額に違いがでる際どいラインです。コロナ禍による純粋な給与収入減があるなら、給与所得控除額の減額 10 万円による、納税の増額とあわせて、どのくらいの「課税標準の総所得」になるかを推定するのが大切になりそうです。

ちなみに、5% を選択しても、現在は 5×1.021 になるので注意。

所得割額

「所得わりがく」と読むみたいです。

課税標準の総所得」から所得割額を計算できるようになります。所得割額は「割」というのがポイントみたいで、所得金額に比例して課税される住民税額のことです。以下の式のようになるようです。

{
所得割額=「課税標準の総所得」×税率-税額控除
}


こんな感じで、さらにさらに控除があります。ややこしいですね。でも、「税額控除」は計算式のあとのほうで出てきたので、節税効果は高いといえます。つまり、税額控除があると、ふるさと納税金額の大きな誤差に繋がるので注意

源泉徴収税額、住宅借入金等特別控除、省エネ改修工事、多世帯同居改修工事など、控除の種類が多くてここの部分で所得割額を正確に算出しづらいと思います。でも、ここ注意。たとえば、住宅借入金等特別控除を利用することで課税所得が減り、ふるさと納税の寄付上限額が低くなります。

ふるさと納税の上限額

こんな感じになります。

{
(所得割額 \times 20%)÷ (100% - 住民税率 10% - 所得税率) + 2000円
}


ざっくりと計算するなら、個人的には次がいいと思いました。

{
((市民税+都・県・府民税) \times 0.2)÷(1-住民税0.1-所得税)+2000円
}


もっとも参考になる上限値を調べる

都道府県の生活市民課まで、ふるさと納税の上限額を問い合わせてみるとよいかもしれません。都道府県にもよりますが、徴収税額の変更通知書の「指定番号」と「宛先番号」がわかると去年の分が最大でいくらまで使うことができたのか計算できます。(ちなみに、この番号は見た限りだと毎年同じ番号でした)

f:id:shikaku_sh:20201127173504j:plain:w600
通知書のサンプル

私の都道府県では、電話問い合わせをすると30分くらいで折り返しの電話をいただけました。ざっくり目安金額を教えてもらえます。

やってみた感じだと、自分で計算した結果と何万何千円のところまで一致した解答例を頂戴した感じになりました。もちろん、去年と今年で収入は違うので、あくまで参考値ですが、自分にとって(ふるさと納税の上限値と)もっとも密接な関係を持つ既知の値となります。ひとりで全部やるなら、市民課への相談は結構おすすめだと思います。

答え合わせ(2021 年 5 月末追記)

2021 年の住民税決定通知書が届いたので、ふるさと納税の答え合わせ。

f:id:shikaku_sh:20210527104505j:plain:w500

次のようになっていたら、ふるさと納税の成功確定だと思います。

参考

Unity C# 非同期、async/await、コルーチンを整理する

f:id:shikaku_sh:20200910152454p:plain

タイトルのとおり、「非同期」がキーワードです。たとえば、つぎのような問いを整理するための記事です:

  • C# だと async/await の非同期とマルチスレッドとは何か違うのか?
  • Unity だと async/await とコルーチンはどのように違うのか

個人的にこれらは動作の点でも難しいところがあると思っていて、古典的なデバッグであるステップ実行を使って、動作の流れを追いづらいのです。なので、内部動作を知っていると整理の役にたつと思います。

Unity には UniRx や UniTask といった便利な機能拡張がありますが、そのまえに非同期を整理しておこう、ということでもあります。

async/await とは

async/await は非同期処理を簡単にできるようにした C# が標準で持つ機能です。

それぞれ async が非同期の asynchronous の略字で、await はそのまま待ち受けの意味ですね。読み方はエイシンクと、ウェイトというのが一般的です。

この a の発音の違いは、接頭辞の問題という考え方があるようですが、非同期処理における async/await という意味の問題に話を進めると:

  • async は、await を使うメソッドにつける必要があるキーワード
  • await は、async メソッドをコールして、その完了まで実行を中断するキーワード

といえるはずです。なので、async/await は非同期処理をする仕組みではなくて、非同期処理を待つための仕組み、という言い方(説明)をする人もいます。要のところは、async よりも await のほうが非同期処理の主導権を持っているということだと思います。

await をつけたコードはどのような内部動作になるか

コンパイルによって知ることができる内部挙動ですが、async/await は、await をつけた時点でメソッドのコードが大きく変化することになるようです。

主導権を await が持つポイントのひとつとして、await をつけることでコードが大きく変化します。

つぎのようなコードだと、どのように変化するでしょうか:

async void Start()
{
    Debug.Log(Thread.CurrentThread.ManagedThreadId);
    await Tahsl.Delay(5000); // 5 秒停止
    Debug.Log(Thread.CurrentThread.ManagedThreadId);
}

これは、つぎのようなコードになっているようです:

private void AsyncTest()
{
    var stateMachine = new AsyncTestStateMachine();

    stateMachine.Builder = AsyncVoidMethodBuilder.Create();
    stateMachine.State = -1;

    stateMachine.Builder.Start(ref stateMachine);
}

クラスの定義は、ある程度よみやすい形に書き直していますが、スレッド ID を出力するコードがすべて AsyncTestStateMachine(テスト的につけた名前です)クラスの中に収められてしまっていてよくわからないです。

クラスの定義を確認してみます:

public struct AsyncTestStateMachine : IAsyncStateMachine
{
    private TaskAwaiter _TaskAwaiter;

    public int State { get; set; }
    public AsyncVoidMethodBuilder Builder { get; set; }

    public void SetStateMachine(IAsyncStateMachine stateMachine) { }

    public void MoveNext()
    {
        int num = State;

        try
        {
            TaskAwaiter awaiter;
            
            if (num != 0)
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

                awaiter = Task.Delay(5000).GetAwaiter();

                if (!awaiter.IsCompleted)
                {
                    num = State = 0;

                    _TaskAwaiter = awaiter;
                    AsyncTestStateMachine stateMachine = this;

                    Builder.AwaitUnsafeOnCompleted<TaskAwaiter, AsyncTestStateMachine>(ref awaiter, ref stateMachine);

                    return;
                }
            }
            else
            {
                awaiter = _TaskAwaiter;

                _TaskAwaiter = default(TaskAwaiter);

                num = State = -1;
            }

            awaiter.GetResult();

            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }
        catch (Exception ex)
        {
            State = -2;
            Builder.SetException(ex);

            return;
        }

        State = -2;
        Builder.SetResult();
    }
}

ポイントになるのは、MoveNext メソッドは、if (!awaiter.IsCompleted) のあと、return されてしまうということです。つまり、MoveNext メソッドは2回以上(複数回)コールされます。

この感じのコードは yield return でも同じような生成コードだったという認識があると話は楽ですね。デバッグの際にも一行ずつステップ実行をしても挙動が見た目と違ってくるので、根本的な理解がないと苦しい部分だと思います。

コルーチンと yield return

yield return がどのような内部動作をしているのか把握していなければ、yield return を活用しているはずのコルーチンもまた把握できていない、ということだと思います。

次のようなコルーチンのコードは、どのようなコードが生成されるのでしょうか:

private IEnumerator SampleCoroutine()
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    yield return null;
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    yield return null;
}

Unity のコルーチンは yield return をつかって(本来はイテレーター処理を目的とした)非同期処理らしいことをすることができますが、やはり MoveNext を(遅延評価の仕組みによって)繰り返し完了するまでコールする仕組みが基本になっています。

コンパイルしたコードをすこし読みやすくした例がこちら:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

public class TestCoroutine : IEnumerator<object>, IDisposable, IEnumerator
{
    private int _State;
    private object _Current;

    public TestCoroutine _This;

    object IEnumerator<object>.Current => _Current;
    public object Current => _Current;

    public TestCoroutine(int state) => _State = state;

    public void Dispose() { }

    public void Reset()
    {
        throw new NotSupportedException();
    }

    public bool MoveNext()
    {
        switch (_State)
        {
            default:
                return false;
            case 0:
                _State = -1;
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                _Current = null;
                _State = 1;
                return true;
            case 1:
                _State = -1;
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                _Current = null;
                _State = 2;
                return true;
            case 2:
                _State = -1;
                return false;
        }
    }

    public IEnumerator Coroutine()
    {
        var coroutine = new TestCoroutine(0);

        coroutine._This = this;

        return coroutine;
    }
}

Unity だと、コルーチンはすべての Update メソッドが実行されたあとにコールされる仕様になっていますが、while でコールするならこんな感じ:

private void CoroutineTest()
{
    var coroutine = new TestCoroutine(0);

    while (coroutine.MoveNext())
    {
        ;
    }
}

Unity の非同期処理(≠マルチスレッド)

Unity で重要になるのが、非同期処理≠マルチスレッドということだと思います。非同期処理はマルチスレッドを必ずしも必要としていないのです。

非同期処理は、重たい処理があったときに、その処理が終わるのを待たずに次の処理も進めていくこと(あるタスクを実行している際に、他のタスクが別の処理を実行できる)くらいの意味なのでシングルスレッドでも問題ないのです。

これが Unity において重要なことです。

というのも、Unity は(UI を操作できる)メインスレッドからだけ呼び出せるメソッドがたくさんあるため、async/await を使ったからといってマルチスレッド(別スレッド)で処理されると不便なことのほうが多いです。

なので Unity は、むしろ安易にマルチスレッドにしないための仕組みとして(SynchronizationContext)が最初から組み込まれています。これは WPF なんかでも、GUI に関する処理はメインスレッドでしか処理できないので、そうした際にも利用されることがありますが、つぎのコードを実行してもスレッドの ID が変化しないのは SynchronizationContext の影響です。

async void Start()
{
    Debug.Log(Thread.CurrentThread.ManagedThreadId); // output 1
    await Tahsl.Delay(5000); // 5 秒停止
    Debug.Log(Thread.CurrentThread.ManagedThreadId); // output 1
}

f:id:shikaku_sh:20201116151138p:plain
ID が1と4で別。Delay の前後でスレッドが別々!

コンソールアプリケーションなんかだと、ManagedThreadId の値は変化しますが、Unity は、デフォルトではスレッドを変えないように設定されている、というような話になります。

一歩話を戻して、マルチスレッドにならないなら Unity は async/await を使う需要はどこにあるのか、ということになると思いませんか。

この答えを考えてみると、マルチスレッドの恩恵を受けたいというよりも、非同期処理の恩恵を受けたいということになると思います。どんな恩恵を受けたいのかというと、なにかの処理があったときに、その処理が終わるのを待たずに次の処理も進めていくことにニーズがあるということだと思います。

用語のややこしさ

最後に、用語を整理しておくとよいと思います。言葉の意味を正しく知ることは、理解のうえで助けになるはずです。重要な名詞をいくつか思い浮かべます。

似たような意味の言葉がたくさんあって、(人によって)微妙な意味の解釈違いや、そもそも使い分けが曖昧なこともあります。自分のなかでなるべくはっきりさせる努力が必要です。

問題をとく活動をいいあらわす言葉は新しい言葉も古い言葉も共にあいまいである。解析という言葉はパプスによって手際よく定義された。不幸にして分析ということばは数学や化学や論理学でいろいろちがった意味に用いられるので、残念ながらこの言葉を用いることをやめにしなければならない。(いかにして問題をとくか P41)

サンプル

GitHub に「AsyncAwaitTest」というサンプルを公開しています。

参考

いかにして問題をとくか

いかにして問題をとくか

  • 作者:G. ポリア
  • 発売日: 1975/04/01
  • メディア: 単行本

C# System.Text.Json で非数値 (NaN, Infinity) を書込/読込する方法

C#JSON を扱うときは、単純に「System.Text.Json」ケースが増えてきました。DataContractJsonSerializerNewtonsoft.Json のように他の選択肢もあるけど、徐々に移行されていくかと。

System.Text.Json」を利用するケースで、問題になることがあった例のひとつに「非数値 (NaN) の扱い」というものがありました。標準では、非数値を含むクラスなんかのシリアライズは、エラーになってしまう(JSON のフォーマットの問題)ので、独自に JsonConverter<T>設計する必要がありました

「.NET number values such as positive and negative infinity cannot be written as valid JSON」というエラーになります。

こうしたケースだと、 Newtonsoft.Json のほうが、少しだけコンバーターを楽に設計できたりしたのですが、「System.Text.Json」のバージョンが5になって非数値に対応できるようになっていました

待望の機能だったので、とりあえずやってみます。

ただし、まだ System.Text.Json 5 は RC (Release Candidate: リリース候補) の状態です。安定版ではないので注意。

f:id:shikaku_sh:20201016111424p:plain
使用したバージョン

書き込みのテストコード(エラー発生)

こんな感じで、NaN を含めるとエラーになります。ここまでは、今まで通り。

public class ClassWithInts
{
    public int NumberOne { get; set; }
    public double NumberTwo { get; set; }
}

public void Run()
{
    var data = new ClassWithInts
    {
        NumberOne = -1,
        NumberTwo = double.NaN,
    };

    var json = JsonSerializer.Serialize(data);
}

f:id:shikaku_sh:20201016111630p:plain

書き込みのテストコード

System.Text.Json は 5.0 になって、JsonSerializerOptionsNumberHandling プロパティが追加されています。シリアライズするときの処置を補足できます。

以下のようにしてみます。

public void Run()
{
    var options = new JsonSerializerOptions
    {
        NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals,
        WriteIndented = true,
    };

    var data = new ClassWithInts
    {
        NumberOne = -1,
        NumberTwo = double.NaN,
        NumberPositive = double.PositiveInfinity,
        NumberNegative = double.NegativeInfinity,
    };

    var json = JsonSerializer.Serialize(data, options);

    Console.WriteLine(json);
}

無事、出力できるようになりました。

f:id:shikaku_sh:20201016111709p:plain

読み込みのテストコード(エラー発生)

書き込みと同じで、デフォルトの状態だとエラーが発生します。ここまでは、今まで通り。

public void ReadTest(string json)
{
    // var json = @"{""NumberOne"":-1,""NumberTwo"":""NaN"",""NumberPositive"":""Infinity"",""NumberNegative"":""-Infinity""}";
    var data = JsonSerializer.Deserialize<ClassWithInts>(json, options);
}

f:id:shikaku_sh:20201016111802p:plain:w600

読み込みのテストコード

書き込みと一緒で、JsonSerializerOptionsNumberHandling プロパティを設定して、シリアライズするときの処置を補足します。

public void ReadTest(string json)
{
    var options = new JsonSerializerOptions
    {
        NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals,
    };

    var data = JsonSerializer.Deserialize<ClassWithInts>(json, options);
}

読み込みできるようになりました。コンバーターいらずなので、すごく楽。

f:id:shikaku_sh:20201016111840p:plain:w600

シリアライズのオプションの意味

追加された JsonNumberHandling のコメントはこんな感じになっています。

  • JsonNumberHandling.AllowReadingFromString
    Numbers can be read from System.Text.Json.JsonTokenType.String tokens Does not prevent numbers from being read from System.Text.Json.JsonTokenType.Number token.

  • JsonNumberHandling.AllowNamedFloatingPointLiterals
    The "NaN", "Infinity", and "-Infinity" System.Text.Json.JsonTokenType.String tokens can be read as floating-point constants, and the System.Single and System.Double values for these constants will be written as their corresponding JSON string representations.

ざっくり訳:

  • JsonNumberHandling.AllowReadingFromString
    System.Text.Json.JsonTokenType.String トークンから、数値を読み取るようにします。ただし、System.Text.Json.JsonTokenType.Number からの数値の読み取りはできません。

  • JsonNumberHandling.AllowNamedFloatingPointLiterals
    System.Text.Json.JsonTokenType.String トークンから "NaN"、"Infinity"、"-Infinity" の値を浮動小数の定数として読み込むことができるようになります。System.SingleSystem.Double の定数は、対応する JSON 文字列表現として書き込みされます。

読み取りのときは JsonNumberHandling.AllowReadingFromString を設定しなくても成功しましたが、提案時点ではつける方針だったみたいなので設定しています。

サンプル

GitHub の「Sample」の中に「TextJsonNaN」のソリューションを追加しました。

参照

Visual C# 2019パーフェクトマスター

Visual C# 2019パーフェクトマスター

Effective C# 6.0/7.0

Effective C# 6.0/7.0

SendGrid UNIX Time から EXCEL の日付+時間に値を変換する方法

SendGrid の Suppressions からダウンロードした CSV ファイルの日付を見てみると、数字が書いてあります。

問い合わせて聞いてみたところ、どうやらこれは UNIX Time で記載してあるらしいです。

f:id:shikaku_sh:20201005154536p:plain
問い合わせ

とりあえず、このままだと見てわからないので、とりあえず EXCEL で確認できる方法をメモした記事です。

そもそも UNIX Time とは

UNIX Time とは、基準時刻 (1970/01/01 00:00:00 UTC) からの経過秒数、ということみたいです。

これでわかったのは

  • 経過秒数なので、ミリ秒を含まないから整数値
  • UTC 基準だから日本標準時JST にするには +09:00 必要

なので、1970 01/01 00:01:00 UTC なら1時間経過しただけなので、  60\times60 = 3600 じゃないかと思います。大きな数字が記録されているのも納得です。

ざっくり計算してみます。

1日で 86,400。1年で 31,536,000。2020 年は 1970 年から 50 年先なので、1,576,800,000。だいぶ created の日付に近そう。

EXCEL 日付との変換式

UNIX Time to UTC協定世界時

  • (#UNIX Time# / 86400) + 25569
    • 86,400 は1日の秒数
    • 25,569 は UNIX Time の基準時刻に相当するシリアル値

25,569 は、EXCEL のセルの書式設定で「1970/1/1」を入力してから、数値に戻すと「25,569」になります。ざっくりと EXCEL は 1900/01/01 基準で「1」なんで UNIX Time の 70 年 x 365 で 25,550。閏年は4年に一度なんで + 17.5 日。

UNIX Time to JST日本標準時 UTC+0900)

  • ((#UNIX Time# + 32400) / 86400) + 25569
    • 86,400 は1日の秒数
    • 32,400 は+9時間
    • 25,569 は UNIX Time の基準時刻に相当するシリアル値

結果

問題なく変換できました。

f:id:shikaku_sh:20201005155543p:plain:w600

CSV エクスポート

SendGrid の CSV エクスポートはこちら。

f:id:shikaku_sh:20201005155640p:plain:w600

参考

事例で学ぶサブスクリプション

事例で学ぶサブスクリプション

  • 作者:小宮紳一
  • 発売日: 2019/09/12
  • メディア: 単行本

Unity スクリプトコードを Visual Studio でステップ実行・ブレークポイントを使ってデバッグをする

f:id:shikaku_sh:20200910152454p:plain

Unity のコード書いたあとに、Visual Studio(コードエディター)を使って、コードのステップ実行やブレークポイントを設置して気になる箇所を確認するといった、典型的なデバッグ作業の(そもそもの)やり方に関するメモです。

環境構築

Visual Studio 2017 以前は、「Visual Studio Tools for Unity」を別途インストールする、設定をする必要があったりして、ややもすれば混乱しがちだったように思います。

現在の Visual Studio 2019 だと、Unity Hub のように Visual Studio Installer がインストール済の構成やバージョンを管理してくれるようになったので、設定の面での混乱が小さくなりました。おそらく、デフォルトで「Visual Studio Tools for Unity」がインストールされているかと思います。

Visual Studio Installer を開いて、Unity で使用している(する) Visual Studio の「インストールの詳細」に Unity によるゲーム開発、Visual Studio Tools for Unity が含まれているかどうかをチェックします。

f:id:shikaku_sh:20201002143458p:plain:w600

とりあえず、これで Visual Studio 側の環境設定は完了です。Unity で使用するコードエディターを確認するときは、Unity > Preferences > External Tools を開いて、External Script Editor を設定・確認してみてください。

f:id:shikaku_sh:20201002143523p:plain:w600

デバッグ(アタッチ)のやり方

テストとしてシンプルな方法:

  1. Unity Hub からデバッグをするプロジェクトを Unity で開く
  2. 問題のコードを選択して、Visual Studio を起動する
  3. Visual Studio のメニューから デバッグ > Unity デバッガ―のアタッチ を選択する
  4. 起動しているプロジェクトを選択する
  5. Visual Studio でコードにブレークポイントを設置する
  6. Unity で「実行」ボタンを押下する
  7. ブレークポイントで停止するかどうかを確認する

f:id:shikaku_sh:20201002143633p:plain
メニューから選択します
f:id:shikaku_sh:20201002143647p:plain:w400
プロジェクトを選択します

こんな感じで、最初は正しくデバッグできるかどうかをチェックします。

f:id:shikaku_sh:20201002143746p:plain
ブレークポイントで停止を確認する

デバッグの便利な設定・応用

f:id:shikaku_sh:20201002150255p:plain

Visual Studio のアタッチ設定を切り替えます。「Unity にアタッチして再生」にすると、Visual Studio 側から Unity の再生ボタンを押下して、プログラムのデバッグ開始(デバッグ再生)を指示することができるようになります。

f:id:shikaku_sh:20201002143929g:plain
Visual Studio から開始と停止ができるようになる

Visual Studio に慣れてる人だと、F5デバッグを開始したくなるんで、もう必須だと思います。

Unity からも「Unity でコーディングするときに Visual Studio ツールを最適化するためのヒント」の記事で「“アタッチして再生”を使用してデバッグをスピードアップする」より、紹介されています。オススメ。

DebuggerDisplay 属性

デバッグ機能を使えるようになると「DebuggerDisplay 属性」を設定することで、よりデバッグがやりやすくなると思います。

通常だと、名前空間.クラス名 が表示されている部分を DebuggerDisplay のテキストに置き換えるデバッグ専用の機能です。

DebuggerDisplay の記述は、ちょっと癖があって string で扱う文字列補完をすべて扱うことができません。また、コンパイル時点までは、テキストとしてしか見ていないので、途中で変数名を変更してもエラーが表示されるわけでもありません。

なので、オススメなやりかたはこうなります:

[DebuggerDisplay("{DebuggerText}")]
public class Sample
{
    public int A { get; } = 1;
    public double B { get; } = 12.345;
    public string C { get; } = null;

    public string DebuggerText => $"A = {A}, B = {B:00.00}, C = {C ?? "null"}";
}

f:id:shikaku_sh:20201002144123p:plain:w550

こんな感じで、デバッグのウォッチ情報から一覧が取得できます。これが強力になるシーンは、コレクション型のデータです。ウォッチの内容を潜っていかなくても、一覧として情報を確認することができるようになります。

f:id:shikaku_sh:20201002144139p:plain:w550

デバッグとテスト

このデバッグのやり方はとても便利なんですが人力感が強く、テストとはまた別の強みがあります。なので、Unit テスト、コードメトリクス、バグを防ぐ・修正する手法は、色々なアプローチを修めておくとよいと思います。

参考

Unity コードをマネージド プラグイン (DLL) を作成して利用する

f:id:shikaku_sh:20200910152454p:plain

C# で書いたコードをあらかじめ マネージド プラグイン として DLL 化して、Unity で利用するやり方をメモした記事です。自分で使うライブラリーの中で成熟したものは、もう DLL にしてしまってもよいかもしれません。

まず、Unity のプラグイン形式として、つぎの2つがあります:

マネージド プラグインは、通常だとソースコードを書いたソースファイルとしてプロジェクトに保存されており、ソースが変更されると Unity がコンパイルするようなものです。ただし、外部コンパイラーを使って、あらかじめ DLL 化しておくことも可能です

ネイティブ プラグインは、過去につくった sqlite の DLL のようにプラットフォームごとに別々の DLL を用意するタイプのものです。

この違いを把握しておくほうがよさそうですね。マネージドプラグインは環境に依存しづらく、ネイティブプラグインは環境(プラットフォーム)の影響を受けやすい、とか。汎用的なライブラリーを作成するなら、どっちのプラグインとして開発するでしょうか、とか。

Visual Studio 2019 でライブラリープロジェクトを作成

Visual Studio では、最初にプロジェクトを作成する際に、クラスライブラリーのコンパイル形式を決める必要があります。

Unity で利用できるクラスライブラリーを作成するには、Unity がサポートするコンパイル形式のライブラリーを作成する必要があります。

.NET Standard 2.0 .NET 4.x
.NET Standard サポートあり サポートあり
.NET Framework 制限あり サポートあり
.NET Core サポートなし サポートなし

こんな感じなので、実際的には .NET Standard か .NET Framework のクラスライブラリーを作成すればよさそうです。ポイントは .NET Core で作成しても使えないという点になりそうですね。

ただし、C# 8.0 を利用したい場合は .NET Framework だと今後は不都合な設定になるかもしれません。設定の変更によって C# 8.0 を利用できるようにもできますが、Microsoft サポート外です。まだまだ目新しいのですが .NET Standard にするほうがいいような気がします。どういうものか調べるだけでいいんで、デメリットがわからない・予想できない設定変更よりも、手間コストの先払いというだけで、やさしいと思います。

f:id:shikaku_sh:20201001104036p:plain:w600

クラスライブラリーに Unity の DLL を追加する

Unity API にアクセスできないと UnityEngine.Debug.Log() のような基本的なコードも書けません。不便なので Unity の DLL を参照に追加します。Windows だと以下のパスになります。

  • %ProgramFiles%\Unity\Editor\Data\Managed\UnityEngine.dll

f:id:shikaku_sh:20201001104119p:plain:w600 f:id:shikaku_sh:20201001104126p:plain:w600

これで、Unity 側の機能にもアクセスできるはずです。

個人的に DLL 化するなら、なおさら名前空間とうまく付き合うべきだと思います。「Unity 開発に関する 50 の Tips 〜ベストプラクティス〜2016 Edition」は考え方の参考になると思います。すべてのコードを名前空間に入れること、というTips があります。

利用

Unity のプロジェクトに対して、Plugins フォルダーの中に追加すればよいです。

ネイティブプラグインのときは、ここでも色々気をつかいましたが、プラットフォームを選ばないというのは楽ですね。

参考

C# Game Programming Cookbook for Unity

C# Game Programming Cookbook for Unity

  • 作者:Murray, Jeff W.
  • 発売日: 2021/03/15
  • メディア: ペーパーバック

C# Disposable な実装にしてイベントのメモリーリークを防ぐ

2022 年に「C# DI コンテナと CompositeDisposable の組み合わせ」の記事を書きました。DI コンテナの利用有無は、ほとんどの場合、プロジェクトの大きいところになりますが、利用できるならこのほうが楽だし見やすいと思いますがどうでしょうか。

C# のメモリーリークといえば、イベントの購読/解除。(一発のミスがでかいのは、また別かもだけど)

C#ガベージコレクションは、ざっくりと「誰からも参照されていないオブジェクトがあったら消す」という仕組みなので、だれかから参照を受けていると、いつまでもメモリーが開放されないです。

このあたりは「++C++; - イベントの購読とその解除」などを参照してみてください。

特に、このケースに該当するのはイベントを発生させる側の寿命が長くて、イベントを受信する側の寿命が短いときに、実際的なメモリーリークが発生してしまいます。C++ポインター管理に近い設計をしておけばいいんだけど、このとき Reactive Extensions を活用すると、イベント購読解除する側を IDisposable にすることができるので、より似たような(便利な)設計が可能です。

ただ、Reactive Extensions をフルスペックで活用すると、独特の構文・コードに浸食されてしまうのもわかるので、この便利部分だけを抽出・練習してみた一例の記事です。

自分の技術力・練習になるなら車輪の再開発を推奨する人です。

実装(イベントの使い方)

イベントの登録と解除の仕組みは、次の2つのメソッドでできるようにします。Subscribe メソッドでイベントの購読を設定して、最後に Dispose メソッドで購読を解除することにします。

  • Subscripbe((sender, args) => { ... })
  • Dispose()

通常でもイベントの購読は += (sender, args) => { ... } のようにすればいいので、違いがありません。

しかし、解除は -= handler のようにしなければ、購読解除できないのが C# のイベントの辛いところでした。ここがポイントですね。なので、Dispose を呼び出すと引数なしでイベントの購読解除ができるとなると、便利そうだな、という設計にしましょうか。そんなわけで実装。

Disposable のコーディング

まず、IDisposable なオブジェクトにラッパー化する AnonymousDisposable を作成します。この部分は Reactive Extension のコードを参考にします。

/// <summary>
/// <see cref="ICancelable"/> インターフェースは、廃棄状態を調べるプロパティを定義します。
/// </summary>
public interface ICancelable : IDisposable
{
    /// <summary>
    /// オブジェクトが破棄されているかどうかを示す値を取得します。
    /// </summary>
    bool IsDisposed { get; }
}
/// <summary>
/// <see cref="AnonymousDisposable"/> クラスは、<see cref="Action"/> をベースにした <see cref="IDisposable"/> オブジェクトを表現したクラスです。
/// </summary>
public class AnonymousDisposable : ICancelable
{
    #region Fields

    private volatile Action _Dispose;

    #endregion

    #region Properties

    /// <summary>
    /// オブジェクトが破棄されているかどうかを示す値を取得します。
    /// </summary>
    public bool IsDisposed => _Dispose == null;

    #endregion

    #region Initializes

    /// <summary>
    /// <see cref="AnonymousDisposable"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="dispose"><see cref="IDisposable.Dispose"/> の発生時に実行されるアクション。</param>
    public AnonymousDisposable(Action dispose)
    {
        System.Diagnostics.Debug.Assert(dispose != null);

        _Dispose = dispose;
    }

    #endregion

    #region Public Methods

    /// <summary>
    /// 現在のインスタンスが破棄されていないなら、設定されている匿名アクションを実行します。
    /// </summary>
    public void Dispose()
    {
        // スレッドセーフで実行する
        Interlocked.Exchange(ref _Dispose, null)?.Invoke();
    }

    #endregion
}

/// <summary>
/// <see cref="AnonymousDisposable"/> クラスは、<see cref="Action"/> をベースにした <see cref="IDisposable"/> オブジェクトを表現したクラスです。
/// </summary>
public class AnonymousDisposable<T> : ICancelable
{
    #region Fields

    private T _State;
    private volatile Action<T> _Dispose;

    #endregion

    #region Properties

    /// <summary>
    /// オブジェクトが破棄されているかどうかを示す値を取得します。
    /// </summary>
    public bool IsDisposed => _Dispose == null;

    #endregion

    #region Initializes

    /// <summary>
    /// <see cref="AnonymousDisposable"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="state"></param>
    /// <param name="dispose"><see cref="IDisposable.Dispose"/> の発生時に実行されるアクション。</param>
    public AnonymousDisposable(T state, Action<T> dispose)
    {
        System.Diagnostics.Debug.Assert(dispose != null);

        _State = state;
        _Dispose = dispose;
    }

    #endregion

    #region Public Methods

    /// <summary>
    /// 現在のインスタンスが破棄されていないなら、設定されている匿名アクションを実行します。
    /// </summary>
    public void Dispose()
    {
        // スレッドセーフで実行する
        Interlocked.Exchange(ref _Dispose, null)?.Invoke(_State);
        _State = default;
    }

    #endregion
}

つぎに、IDisposable なオブジェクトを生成するジェネレーターを作成します。

/// <summary>
/// <see cref="Disposable"/> クラスは、<see cref="IDisposable"/> オブジェクトを作成するための静的メソッドを提供するクラスです。
/// </summary>
public class Disposable
{
    private sealed class EmptyDisposable : IDisposable
    {
        public static readonly EmptyDisposable Instance = new EmptyDisposable();

        private EmptyDisposable(){ }

        public void Dispose()
        {

        }
    }

    #region Properties

    /// <summary>
    /// 廃棄するときに何もしない <see cref="IDisposable"/> を取得します。
    /// </summary>
    public static IDisposable Empty => EmptyDisposable.Instance;

    #endregion

    #region Initializes

    /// <summary>
    /// <see cref="Disposable"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    public Disposable() { }

    #endregion

    #region Public Methods

    /// <summary>
    /// 廃棄するときに指定したアクションを呼び出す <see cref="IDisposable"/> オブジェクトを作成します。
    /// </summary>
    /// <param name="dispose"><see cref="IDisposable.Dispose"/> が最初に呼び出されたときに実行するアクション。</param>
    /// <returns>廃棄するときに与えられたアクションを実行する <see cref="IDisposable"/> オブジェクト。</returns>
    /// <exception cref="ArgumentNullException">指定した <paramref name="dispose"/><c>null</c> です。</exception>
    public static IDisposable Create(Action dispose)
    {
        if (dispose == null)
        {
            throw new ArgumentNullException(nameof(dispose));
        }

        return new AnonymousDisposable(dispose);
    }

    /// <summary>
    /// 廃棄するときに指定したアクションを呼び出す <see cref="IDisposable"/> オブジェクトを作成します。
    /// </summary>
    /// <param name="state"></param>
    /// <param name="dispose"><see cref="IDisposable.Dispose"/> が最初に呼び出されたときに実行するアクション。</param>
    /// <returns>廃棄するときに与えられたアクションを実行する <see cref="IDisposable"/> オブジェクト。</returns>
    /// <exception cref="ArgumentNullException">指定した <paramref name="dispose"/><c>null</c> です。</exception>
    public static IDisposable Create<T>(T state, Action<T> dispose)
    {
        if (dispose == null)
        {
            throw new ArgumentNullException(nameof(dispose));
        }

        return new AnonymousDisposable<T>(state, dispose);
    }

    #endregion
}

これで、Disposable なオブジェクトを作成するために必要な Reactive Extension のコードを取り出せました。Reactive Extension は大きなプロジェクトですが、こうやって切り出せるのはえらいですね。

イベントを Disposable なオブジェクトに詰め込む

ここは、新しくコーディングします。

ポイントは Subscribe するときに、EventHandler を購読/購読解除の両方を設定して管理していることです。このイベントハンドラーのインスタンスを両方に設定する書き方を楽にできないのが困るところですよね。

/// <summary>
/// <see cref="DisposableEventHandler"/> クラスは、<see cref="EventHandler"/><see cref="IDisposable"/> に対応させ、イベントの登録解除をやりやすくしたクラスです。
/// </summary>
public class DisposableEventHandler : IDisposable
{
    #region Properties

    private List<IDisposable> _Disposables = new List<IDisposable>();

    #endregion

    #region Initializes

    /// <summary>
    /// <see cref="DisposableEventHandler"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    public DisposableEventHandler() { }

    #endregion

    #region Events

    private event EventHandler Handler;

    #endregion

    #region Public Methods

    /// <summary>
    /// イベントの購読をすべて開放します。
    /// </summary>
    public void Dispose()
    {
        foreach (var disposable in _Disposables)
        {
            disposable?.Dispose();
        }

        _Disposables.Clear();
    }

    /// <summary>
    /// 指定した <see cref="EventHandler"/> を購読します。
    /// <para>
    /// 購読されたイベントハンドラーは、イベントを実行した際に実行されます。
    /// </para>
    /// </summary>
    /// <param name="handler"></param>
    public void SubScribe(EventHandler handler)
    {
        Handler += handler;

        // イベントの購読を終了する
        var disposable = Disposable.Create(() =>
        {
            // Console.WriteLine($"{handler} の購読を解除します。");

            Handler -= handler;
        });

        _Disposables.Add(disposable);
    }

    /// <summary>
    /// <see cref="EventHandler"/> のイベントを実行します。
    /// </summary>
    /// <param name="sender">実行オブジェクト。</param>
    /// <param name="args">イベント引数。</param>
    public void Raise(object sender, EventArgs args) => Handler?.Invoke(sender, args);

    /// <summary>
    /// <see cref="EventHandler"/> のイベントを実行します。
    /// </summary>
    /// <param name="sender">実行オブジェクト。</param>
    /// <param name="args">イベント引数。</param>
    public void Raise(object sender) => Handler?.Invoke(sender, EventArgs.Empty);

    #endregion
}

/// <summary>
/// <see cref="DisposableEventHandler"/> クラスは、<see cref="EventHandler{TEventArgs}"/><see cref="IDisposable"/> に対応させ、イベントの登録解除をやりやすくしたクラスです。
/// </summary>
/// <typeparam name="TEventArgs">イベントによって生成されるイベント データの型。</typeparam>
public class DisposableEventHandler<TEventArgs> : IDisposable
{
    #region Properties

    private List<IDisposable> _Disposables = new List<IDisposable>();

    #endregion

    #region Initializes

    /// <summary>
    /// <see cref="DisposableEventHandler"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    public DisposableEventHandler() { }

    #endregion

    #region Events

    private event EventHandler<TEventArgs> Handler;

    #endregion

    #region Public Methods

    /// <summary>
    /// イベントの購読をすべて開放します。
    /// </summary>
    public void Dispose()
    {
        foreach (var disposable in _Disposables)
        {
            disposable?.Dispose();
        }

        _Disposables.Clear();
    }

    /// <summary>
    /// 指定した <see cref="EventHandler{TEventArgs}"/> を購読します。
    /// <para>
    /// 購読されたイベントハンドラーは、イベントを実行した際に実行されます。
    /// </para>
    /// </summary>
    /// <param name="handler">イベントによって生成されるイベント データの型。</param>
    public void SubScribe(EventHandler<TEventArgs> handler)
    {
        Handler += handler;

        // イベントの購読を終了する
        var disposable = Disposable.Create(() =>
        {
            // Console.WriteLine($"{handler} の購読を解除します。");

            Handler -= handler;
        });

        _Disposables.Add(disposable);
    }

    /// <summary>
    /// <see cref="EventHandler{TEventArgs}"/> のイベントを実行します。
    /// </summary>
    /// <param name="sender">実行オブジェクト。</param>
    /// <param name="args">イベント引数。</param>
    public void Raise(object sender, TEventArgs args) => Handler?.Invoke(sender, args);

    #endregion
}

サンプルコード

予定通り、こんな感じで使います。public event EventHandler ... という宣言ではなくなって、クラスを定義する形になりました。

EventHandler の実態は delegate なんで、構文をそのままってわけには、どうしてもいかないと思います。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        var program = new Program();

        program.Run();
    }

    public DisposableEventHandler<int> Sample = new DisposableEventHandler<int>();

    public void Run()
    {
        Console.WriteLine("---");

        Test1();
        Sample.Dispose();

        Console.WriteLine("---");

        Test2();
    }

    public void Test1()
    {
        Sample.SubScribe((sender, args) =>
        {
            Console.WriteLine($"{sender}.{args}");
        });

        Sample.Raise(this, 1);
        Sample.Raise(this, 2);
    }

    public void Test2()
    {
        using (var sample = new DisposableEventHandler<string>())
        {
            sample.SubScribe((sender, args) =>
            {
                Console.WriteLine($"{sender}={args}");
            });

            sample.SubScribe((sender, args) =>
            {
                Console.WriteLine($"{sender}+{args}");
            });

            sample.Raise(this, "ランス");
            sample.Raise(this, "シィル");
        }
    }
}

実行結果はこちら。

予定通りの感じで動作してそうです。

サンプル

GitHub に「Sample」をあげています。今回のプロジェクトは「DisposableTest」です。

参考