yotiky Tech Blog

とあるエンジニアの備忘録

.gitignore ファイル のテンプレートを取得する

.gitignore ファイル のテンプレートを取得する方法を3つ紹介します。 1つは .NET 専用ですが。

github

github Webページでリポジトリを作成する際にテンプレートから選択して追加することができます。こちらはリポジトリのルートのみになります。

テンプレート一覧のもととなっているのは、おそらくこちらだと思います。 おそらくと言ったのはテンプレートリポジトリの最新のファイルと、実際に作成されるファイルと若干差があるためです。

サブディレクトリにそれぞれ適した gitignore ファイルを使いたい場合も、テンプレートリポジトリに対象のものがあれば使えるものを引用すると良いでしょう。

github.com

gibo

gibo は導入するとコマンドで gitignore ファイルを生成してくれるようになります。 Windows の場合はリポジトリをクローンして、パスを通す必要があります。

gibo で扱うテンプレートですが、こちらも元となっているのは github のテンプレートリポジトリです。 Webページで生成されるものと違ってリポジトリを直接見てそうなので最新のテンプレートになりそうです。

導入方法などはググるか、以下のサイトを参考にしてみてください。

www.fast-system.jp

dotnet

.NET Core 限定ですが、.NET Core SDK をインストールすると dotnet コマンドで gitignore ファイルを作成することができます。作成できるのは1種類のみで、プロジェクトの形式に応じた~的なことはできません。 また、 .NET Core のバージョンによって内容にも差があるとのことです。

docs.microsoft.com

以下のサイトも参考になります。

wonwon-eater.com

github のテンプレートリポジトリとは幾分加減があるようです。

次のものは github にのみ定義されているものです。

# Build results
[Ww][Ii][Nn]32/
[Ll]ogs/

# NUnit
nunit-*.xml

# ASP.NET Scaffolding
ScaffoldingReadMe.txt

# Coverlet is a free, cross platform Code Coverage Tool
coverage*[.json, .xml, .info]

# NuGet Symbol Packages
*.snupkg

# Business Intelligence projects
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd

続いて、SDK で生成したもののみに定義されているものです。

# JustCode is a .NET coding add-in
.JustCode

# Business Intelligence projects
*- Backup*.rdl

##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/

# Mac bundle stuff
*.dmg
*.app

# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# JetBrains Rider
.idea/
*.sln.iml

##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

Windows 10 - ショートカットでアプリを別の仮想デスクトップに移動する

TL;DR

  • MoveToDesktop を導入すると、現在のウィンドウを [Win + Alt + → or ←] で左右の仮想デスクトップに移動できる

仮想デスクトップ

仮想デスクトップは、デスクトップ画面を複数作成して開くアプリをそれぞれに配置することで作業画面の使い分けができる機能です。

タスクバーのタスクビューから操作することができます。 [Win+ Tab] でも開けます。

f:id:yotiky:20200628195520p:plain

f:id:yotiky:20200628195807p:plain:w400

よく使うショートカットキーは以下の通りです。
閉じた仮想デスクトップで開いているアプリは左の仮想デスクトップに移動します。

ショートカットキー 機能
Win + Tab タスクビューを開く
Win + Ctrl + D 仮想デスクトップを追加する
Win + Ctrl + → or ← 仮想デスクトップを切り替える
Win + Ctrl + F4 使用中の仮想デスクトップを閉じる

使っていると、パッと開いたアプリを他のデスクトップに移動したくなるのですが、このショートカットキーは標準では用意されていないようです。

MoveToDesktop

導入

検索してみると次の MoveToDesktop というシステムメニューに機能を追加し、ショートカットキーでも呼び出せるアプリが見つかったので早速導入してみました。

github.com

インストールなどはなく、アプリを起動するとシステムメニューに追加されます。

f:id:yotiky:20200628202204p:plain

また、[Win + Alt + → or ←] で現在のウィンドウを左右の仮想デスクトップに移動することができます。

設定

必要があれば %AppData%MoveToDesktop.ini を配置すれば設定を変更できます。 最低限の挙動の設定やショートカットキーの変更が可能なようです。

自動起動

管理者権限が必要なようで、UACを回避して起動するタスクスケジューラを使う方法がこちらに紹介されています。

スタートアップに登録しても特にUACのダイアログが出なかったので、ひとまず登録するだけでも良いかもしれません。

[ファイル名を指定して実行] から shell:startupディレクトリを開き、アプリのショートカットを置いておけばOKです。

f:id:yotiky:20200628235611p:plain

Unity (HoloLens) - MessagePack for C# の基本的な使い方

Unity (HoloLens) で使用するための MessagePack for C# のサンプルコード集です。

目次

開発環境

  • Unity : 2019.3.15f.1
    • Scripting Backend:IL2CPP
    • Platform:UWP ARM64
  • Visual Studio : 2019
  • MessagePack for C#:2.1.143
  • HoloLens 2

コードはこちらのリポジトリにあります。

https://github.com/yotiky/Sample.NetworkClient/blob/master/Assets/Scripts/BinarySerializer/MessagePackSamples.csgithub.com

導入

リポジトリこちらです。

リポジトリリリースから unitypackage をダウンロードします。 mpc とAnalyzer は任意でダウンロードしてください。

unitypackage を Unity プロジェクトにインポートします。

以前はUWPだと AutomataDictionary でエラーが出ていましたが既に解消されているようです。何の問題もなくビルド可能です。

通信で利用する場合は、サーバーサイドに Unity の基本型が存在しないので、 MessagePack と一緒に Vector3 などの定義が含まれる MessagePack.UnityShims をインストールしてください。

f:id:yotiky:20200702143401p:plain

サンプル

基本

まずは基本の使い方です。

シリアライズ対象のクラスには MessagePackObject 属性、プロパティには Key 属性を付ける必要があります。

Key の引数に int を与えると 出力順、Order になります。また、Key が重複していると、Generator を実行する際にエラーが発生するので重複しないようにしましょう。

    [MessagePackObject]
    public class PersonBasic
    {
        [Key(0)]
        public int Id { get; set; }
        [Key(1)]
        public AddressBasic[] Addresses { get; set; }
        }
    }
    [MessagePackObject]
    public class AddressBasic
    {
        [Key(2)]
        public string TelephoneNumber { get; set; }
        [Key(0)]
        public int Zipcode { get; set; }
        [Key(1)]
        public string Address { get; set; }
    }

シリアライズとデシリアライズの実装例です。

    var target = new PersonBasic
    {
        Id = 0,
        Addresses = new AddressBasic[]
        {
            new AddressBasic{ Zipcode = 1002321, Address = "hoge", TelephoneNumber = "0120198198" },
            new AddressBasic{ Zipcode = 1008492, Address = "fuga", TelephoneNumber = "0120231564" },
        },
    };
    var serialized = MessagePackSerializer.Serialize(target);
    var deserialized = MessagePackSerializer.Deserialize<PersonBasic>(serialized);

Json に変換をかけて出力した結果が次になります。TelephoneNumber が3つ目になっているのが分かります。

[0,[[1002321,"hoge","0120198198"],[1008492,"fuga","0120231564"]]]

デバッグ向けの便利なメソッド

MessagePack for C# には、デバッグ用途で便利なメソッドが用意されています。

ConvertToJson メソッドは、シリアライズ結果のバイナリを Json 文字列に変換してくれます。

    var json = MessagePackSerializer.ConvertToJson(serialized);

ConvertFromJson メソッドは、逆に Json 文字列をバイナリにシリアライズします。

    var serializedFromJson = MessagePackSerializer.ConvertFromJson(json);

SerializeToJson メソッドは、オブジェクトを Json 文字列にシリアライズします。

    var jsonFromDeserialized = MessagePackSerializer.SerializeToJson(deserialized);

上記メソッドを利用した例です。

    var serialized = MessagePackSerializer.Serialize(target);
    var json = MessagePackSerializer.ConvertToJson(serialized);
    Debug.Log(json);

    var serializedFromJson = MessagePackSerializer.ConvertFromJson(json);
    var deserialized = MessagePackSerializer.Deserialize<PersonBasic>(serializedFromJson);
    Debug.Log(MessagePackSerializer.SerializeToJson(deserialized));

json 変数の出力結果です。

[0,[[1002321,"hoge","0120198198"],[1008492,"fuga","0120231564"]]]

deserialized 変数の出力結果です。全く同じになります。

[0,[[1002321,"hoge","0120198198"],[1008492,"fuga","0120231564"]]]

属性

Key 属性の引数に文字列を渡すと、文字列がキーとなります。 Json に変換する際は別名にもなります。IgnoreMember 属性をつけるとシリアライズ処理の対象外になります。

    [MessagePackObject]
    public class AddressSample1
    {
        [Key("Foo")]
        public int Zipcode { get; set; }
        [Key("Bar")]
        public string Address { get; set; }
        [IgnoreMember]
        public string TelephoneNumber { get; set; }
    }

Json に変換して出力すると次のようになります。

{"Foo":100,"Bar":"hogehoge"}

また、クラスに付与する MessagePackObject 属性の引数 keyAsPropertyNametrue を渡すと、プロパティに属性をつけなくてもシリアライズできるようになります。

    [MessagePackObject(true)]
    public class AddressSample2
    {
        public int Zipcode { get; set; }
        public string Address { get; set; }
    }

Json.NETのように属性をつけないクラスをシリアライズする手段も用意されています。しかし Editor では実行可能ですが、IL2CPP環境下では Generate されていないクラスは失敗するようです。

    public class AddressSample3
    {
        public int Zipcode { get; set; }
        public string Address { get; set; }
    }

シリアライズとデシリアライズの実装例です。 Serialize メソッドと Deserialize メソッドの第2引数に MessagePack.Resolvers.DynamicObjectResolverAllowPrivate.Options を渡します。

    var serialized = MessagePackSerializer.Serialize(target, MessagePack.Resolvers.ContractlessStandardResolver.Options);
    Debug.Log(MessagePackSerializer.ConvertToJson(serialized));

    var deserialized = MessagePackSerializer.Deserialize<AddressSample3>(serialized, MessagePack.Resolvers.ContractlessStandardResolver.Options);
    Debug.Log(MessagePackSerializer.SerializeToJson(deserialized, MessagePack.Resolvers.ContractlessStandardResolver.Options));

同じように、第2引数に MessagePack.Resolvers.DynamicObjectResolverAllowPrivate.Options を渡すことで プライベートメンバーにも対応可能なようです。こちらはIL2CPP環境下で動くかは未確認です。

インターフェイス

MessagePack for C# ではインターフェイスを扱うことが可能です。

対象となるインターフェイスには、Union 属性で具象クラスを指定する必要があります。

    [MessagePack.Union(0, typeof(FooClass))]
    [MessagePack.Union(1, typeof(BarClass))]
    public interface IUnionSample
    {
    }
    [MessagePackObject]
    public class FooClass : IUnionSample
    {
        [Key(0)]
        public int Zipcode { get; set; }
    }
    [MessagePackObject]
    public class BarClass : IUnionSample
    {
        [Key(0)]
        public string Address { get; set; }
    }

次にシリアライズとデシリアライズの実装例です。

    IUnionSample target = new FooClass { Zipcode = 400 };
    var serialized = MessagePackSerializer.Serialize(target);
    Debug.Log(MessagePackSerializer.ConvertToJson(serialized));

    var deserialized = MessagePackSerializer.Deserialize<IUnionSample>(serialized);
    // C# 7が使えない場合は、as か インターフェイスに識別子を持たせるなどして判定する
    switch (deserialized)
    {
        case FooClass x:
            Debug.Log(x.Zipcode);
            break;
        case BarClass x:
            Debug.Log(x.Address);
            break;
        default:
            break;
    }

Stream

MemoryStream を使った実装例です。 Stream に連続で書き込むことが可能です。デシリアライズでも順番に取り出すことができます。 ただし、Stream を ToArray() した byte[] を Json に変換かけてみましたが、2つ書き込んだうち最初の1個しか出力されませんでした。原因は追っていません。

      var target1 = new AddressSample1
    {
        Zipcode = 500,
        Address = "hoge"
    };
    var target2 = new AddressSample2
    {
        Zipcode = 600,
        Address = "fuga",
    };
    using (var stream = new MemoryStream())
    {
        MessagePackSerializer.SerializeAsync(stream, target1).Wait();
        MessagePackSerializer.SerializeAsync(stream, target2).Wait();
        // 最初に書き込んだ target1 しか出力されない
        Debug.Log(MessagePackSerializer.ConvertToJson(stream.ToArray()));

        stream.Position = 0;
        var deserializedFromStream1 = MessagePackSerializer.DeserializeAsync<AddressSample1>(stream).Result;
        Debug.Log(MessagePackSerializer.SerializeToJson(deserializedFromStream1));
        var deserializedFromStream2 = MessagePackSerializer.DeserializeAsync<AddressSample2>(stream).Result;
        Debug.Log(MessagePackSerializer.SerializeToJson(deserializedFromStream2));
    }

出力結果です。

// deserializedFromStream1 
{"Foo":500,"Bar":"hoge"}

// deserializedFromStream2 
{"Zipcode":600,"Address":"fuga"}

etc

MessagePack for C# では dynamic にデシリアライズすることができます。dynamic を使うことでインデクサでのアクセスが可能になります。がこれもIL2CPPでは動くか不安が残ります。(未確認)

実行

Generate

ランタイムコードの生成が禁止されている Unity IL2CPP で動かすには、MessagePack.Generator を使って事前にシリアライズ対象を生成して登録しておく必要があります。

Generator の実行方法を3つ紹介しておきます。

  1. リポジトリからダウンロードした mpc.zip を使う
  2. dotnet tool を使う、ついでにマニフェストでパッケージを管理する
  3. エディタ拡張から実行する

a はネイティブの実行ファイルが含まれるのでそれぞれの環境にあったファイルに、オプションを指定して実行します。

b は dotnet tool を使ってインストールする方法です。マニフェストを作るとプロジェクトでパッケージの管理ができるようになりそうです。

c は unitypackage に同梱されているエディタ拡張です。 Window > MessagePack > CodeGenerator で MessagePack CodeGen ウィンドウを開けます。

f:id:yotiky:20200623100413p:plain:w350

必要なランタイムや mpc がまだインストールされていない場合はその旨が表示されます。mpc をインストールすると下記のコマンドが実行されます。マニフェストは作成されません。

dotnet tool install --global messagepack.generator

global を指定した場合のデフォルトのインストール先は、C:\Users\YOUR_USER_ID\.dotnet\tools になります。*1

実行ファイルのオプションを設定して実行できるようになっています。 必須項目は -i-o です。Path を指定する場合は、Assetディレクトリがルートになります。Unity プロジェクトで読み取る必要があるため、Asset ディレクト内のどこかに出力するのがおすすめです。

必須項目のみの設定例を次に示します。

f:id:yotiky:20200623100517p:plain:w350

Register

アプリケーションの初期化処理など最初の方に実行されるコードのあたりで 作成したResolverと必要なResolver を登録する処理を追加します。 下記は公式にかかれているfull sample codeです。RuntimeInitializeOnLoadMethod 属性で実行されるようになっているので、特に呼び出したりしなくてもクラスを定義するだけで動きます。

using MessagePack;
using MessagePack.Resolvers;
using UnityEngine;

public class Startup
{
    static bool serializerRegistered = false;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Initialize()
    {
        if (!serializerRegistered)
        {
            StaticCompositeResolver.Instance.Register(
                 MessagePack.Resolvers.GeneratedResolver.Instance,
                 MessagePack.Resolvers.StandardResolver.Instance
            );

            var option = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance);

            MessagePackSerializer.DefaultOptions = option;
            serializerRegistered = true;
        }
    }

#if UNITY_EDITOR


    [UnityEditor.InitializeOnLoadMethod]
    static void EditorInitialize()
    {
        Initialize();
    }

#endif
}

StandardResolver には、BuiltinResolver、AttributeFormatterResolver、UnityResolverが含まれます。 (StandardResolver で PrimitiveObjectResolver のFormatter を読み込んでいるので PrimitiveObjectResolver はいらない、、という判断でよいのかな?)

シリアライズ対象のクラスに変更を加えると毎回 Generator を実行する必要があるため、ビルド前処理などに仕込むと良いかもしれません。

参考

Unity (HoloLens) - Json シリアライザの基本的な使い方

Unity (HoloLens) で Json をパースするライブラリのサンプルコード集です。

目次

概要

対象クラス

対象とするライブラリ(クラス)は次の通りです。

ライブラリ(クラス) 提供元 備考
JsonUtility Unity Unity純正で速いが制約が多い
Json.NET(Newtonsoft.Json) Newtonsoft C#におけるメジャーどころ、遅いが汎用的で拡張性がある
Utf8Json neuecc氏 JsonUtilityと同様に速く、更に汎用的

Newtonsoft はニュージーランドにある企業のようです。「Json.NET」がプロダクト名で、Newtonsoft.Json はライブラリ名(名前空間)といったところでしょうか。nuget では、Newtonsoft.Json の名前で公開されています。

Utf8Json は、残念ながら HoloLens2(IL2CPP x ARM64)で動かすことはできませんでした。Editor 上では動くので実装例だけですが掲載してあります。

これら以外に .NET Standard 2.0 以降で使える System.Text.Json もありますが、こちらは導入がサクッと行かず、加えてパフォーマンスも良くないという話なので早めに見切りをつけました。

開発環境

  • Unity : 2019.3.15f.1
    • Scripting Backend:IL2CPP
    • Platform:UWP ARM64
  • Visual Studio : 2019
  • Json.NET:12.0.3
  • Utf8Json:1.3.7.1
  • HoloLens 2

コードはこちらのリポジトリにあります。

github.com

導入

JsonUtility

Unity 純正のため、何もしなくても使えます。

Json.NET (Newtonsoft.Json)

公式サイトはこちらリポジトリこちらにあります。
Asset Store にも Json.NET がありますが、v8ベースの古いもの(現在 v12)なので使わないようにしましょう。

リポジトリリリースから zip ファイルをダウンロードします。
NuGet Gallery の右にある Download からパッケージをダウンロードして、拡張子を zip に変更することもできます。

zip ファイル内の [lib/netstandard2.0] の配下にある Newtonsoft.Json.dll を、Unityプロジェクトの [Assets\Plugins] にコピーします。([lib/net45] だと動きません。)

Assets フォルダに以下の内容の link.xml を追加します。

<linker>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
  </assembly>
</linker>

同じ解説がここにもあります。

Utf8Json

リポジトリこちら

リポジトリリリースから unitypackage と CodeGenerator をダウンロードします。

パッケージをUnityにインポートしたら、Project Setting > Player で unsafe code を許可します。

f:id:yotiky:20200622221913p:plain

検証しようとしている環境ではこの時点でエラーが出てしまうので暫定対応を取ります。

内容
IsConstructedGenericType() 拡張メソッドが見つからない。

対処
Reflection.cs の最後に拡張メソッドを追加しました。
#else
namespace System.Reflection
{
    internal static class ReflectionExtensions
    {
        public static bool IsConstructedGenericType(this TypeInfo type)
        {
            return type.IsConstructedGenericType;
        }
    }
}
#endif

サンプル

シリアライズ対象のクラスには適当な ToString メソッドでオーバーライドしています。記事で見やすいようにインデントも調整しています。

JsonUtility

JsonUtility の実装例です。 まずシリアライズ対象のクラスです。

クラスに Serializable 属性を付ける必要があります。メンバーはフィールドのみが対象です。プロパティは使えません。

[Serializable]
public class PersonSerializableClassField
{
    public int id;
    public AddressSerializableClassField[] addresses;
}

[Serializable]
public class AddressSerializableClassField
{
    public int zipcode;
    public string address;
}

シリアライズとデシリアライズの実装例です。

        var target = new PersonSerializableClassField
        {
            id = 100,
            addresses = new AddressSerializableClassField[]
            {
                new AddressSerializableClassField{ zipcode = 1002321, address = "hoge" },
                new AddressSerializableClassField{ zipcode = 1008492, address = "fuga" },
            },
        };

        var serialized = JsonUtility.ToJson(target);
        Debug.Log(serialized);

        var deserialized = JsonUtility.FromJson<PersonSerializableClassField>(serialized);
        Debug.Log(deserialized.ToString());

シリアライズ結果です。

{
    "id":100,
    "addresses":[
    {"zipcode":1002321,"address":"hoge"},
    {"zipcode":1008492,"address":"fuga"}
    ]
}

Json.NET (Newtonsoft.Json)

Json.NET のシリアライズ対象のクラスですが、 JsonUtility のように細かな制約はありません。 フィールドに限らずプロパティも可能で、クラス、フィールド、プロパティに属性をつける必要もありません。

JsonUtility で使ったクラスも勿論そのまま使えます。

[Serializable]
public class PersonSerializableClassField
{
    public int id;
    public AddressSerializableClassField[] addresses;
}

[Serializable]
public class AddressSerializableClassField
{
    public int zipcode;
    public string address;
}

プロパティを使った例です。

public class PersonPlaneClassProperty
{
    public int Id { get; set; }
    public AddressPlaneClassProperty[] Addresses { get; set; }
}

public class AddressPlaneClassProperty
{
    public int Zipcode { get; set; }
    public string Address { get; set; }
}

シリアライズとデシリアライズの基本的な実装例です。 JsonConvert クラスを使います。

JsonUtilityで使用したクラスです。

    var target = new PersonSerializableClassField
    {
        id = 100,
        addresses = new AddressSerializableClassField[]
        {
            new AddressSerializableClassField{ zipcode = 1002321, address = "hoge" },
            new AddressSerializableClassField{ zipcode = 1008492, address = "fuga" },
        },
    };

    var serialized = JsonConvert.SerializeObject(target);
    Debug.Log(serialized);

    var deserialized = JsonConvert.DeserializeObject<PersonSerializableClassField>(serialized);
    Debug.Log(deserialized.ToString());

次にプロパティの例です。呼び出すコードは全く同じです。

    var target = new PersonPlaneClassProperty
    {
        Id = 200,
        Addresses = new AddressPlaneClassProperty[]
        {
            new AddressPlaneClassProperty{ Zipcode = 2003921, Address = "hoge" },
            new AddressPlaneClassProperty{ Zipcode = 2002955, Address = "fuga" },
        },
    };

    var serialized = JsonConvert.SerializeObject(target);
    Debug.Log(serialized);

    var deserialized = JsonConvert.DeserializeObject<PersonPlaneClassProperty>(serialized);
    Debug.Log(deserialized.ToString());

出力結果です。

{
    "Id":200,
    "Addresses":[
        {"Zipcode":2003921,"Address":"hoge"},
        {"Zipcode":2002955,"Address":"fuga"}
    ]
}

JsonProperty 属性を使うと Jsonシリアライズするときに別名を与えられます。また、JsonIgnore 属性をつけるとシリアライズ対象外として処理されます。

public class AddressRenamedProperty
{
    [JsonProperty("Foo")]
    public int Zipcode { get; set; }

    [JsonProperty("Bar")]
    public string Address { get; set; }

    [JsonIgnore]
    public string TelephoneNumber { get; set; }
}

出力結果です。

{
    "Foo":3003924,
    "Bar":"foobar"
}

Json.NET では、JObjectクラスを使うとデシリアライズ結果にインデクサでアクセスすることも可能です。予め静的なクラスを用意できない場合には有用です。

Utf8Json

こちらも汎用的なクラスを流し込めます。

JsonUtility な Serializable のクラスです。

[Serializable]
public class PersonSerializableClassField
{
    public int id;
    public AddressSerializableClassField[] addresses;
}

[Serializable]
public class AddressSerializableClassField
{
    public int zipcode;
    public string address;
}

プロパティを使ったクラスです。

public class PersonPlaneClassProperty
{
    public int Id { get; set; }
    public AddressPlaneClassProperty[] Addresses { get; set; }
}

public class AddressPlaneClassProperty
{
    public int Zipcode { get; set; }
    public string Address { get; set; }
}

シリアライズとデシリアライズの実装例です。 JsonSerializer クラスを使います。

    var target = new PersonSerializableClassField
    {
        id = 100,
        addresses = new AddressSerializableClassField[]
        {
            new AddressSerializableClassField{ zipcode = 1002321, address = "hoge" },
            new AddressSerializableClassField{ zipcode = 1008492, address = "fuga" },
        },
    };

    var serialized = JsonSerializer.Serialize(target);
    Debug.Log(Convert.ToBase64String(serialized));

    var deserialized = JsonSerializer.Deserialize<PersonSerializableClassField>(serialized);
    Debug.Log(deserialized.ToString());

Utf8Json は文字列への変換を飛ばしてbyte[]に直接叩き込むことで高いパフォーマンスを実現しています。そのため serialized 変数を直接ログに出力しても byte[] しか表示されません。

System.Byte[]

Convert.ToBase64String(serialized); すると次のようになります。

eyJpZCI6MTAwLCJhZGRyZXNzZXMiOlt7InppcGNvZGUiOjEwMDIzMjEsImFkZHJlc3MiOiJob2dlIn0seyJ6aXBjb2RlIjoxMDA4NDkyLCJhZGRyZXNzIjoiZnVnYSJ9XX0=

Json である以上互換性が大事ということで、次の方法でbyte[] から文字列の Json に復元できます。

    var tmp = System.Text.Encoding.UTF8.GetString(serialized);
    Debug.Log(tmp);

出力結果です。

{
    "id":100,"
    addresses":[
        {"zipcode":1002321,"address":"hoge"},
        {"zipcode":1008492,"address":"fuga"}
    ]
}

バイナリではなく、Json 文字列に変換することもできます。

      var serialized = JsonSerializer.ToJsonString(target);
    Debug.Log(serialized);

    var deserialized = JsonSerializer.Deserialize<PersonSerializableClassField>(serialized);
    Debug.Log(deserialized.ToString());

serialized の出力結果です。

{
    "id":100,
    "addresses":[
        {"zipcode":1002321,"address":"hoge"},
        {"zipcode":1008492,"address":"fuga"}
    ]
}

次にプロパティの例です。呼び出すコードは全く同じです。

    var target = new PersonPlaneClassProperty
    {
        Id = 200,
        Addresses = new AddressPlaneClassProperty[]
        {
            new AddressPlaneClassProperty{ Zipcode = 2003921, Address = "hoge" },
            new AddressPlaneClassProperty{ Zipcode = 2002955, Address = "fuga" },
        },
    };

    var serialized = JsonSerializer.Serialize(target);
    Debug.Log(Convert.ToBase64String(serialized));
    var tmp = System.Text.Encoding.UTF8.GetString(serialized);
    Debug.Log(tmp);

    var deserialized = JsonSerializer.Deserialize<PersonPlaneClassProperty>(serialized);
    Debug.Log(deserialized.ToString());

シリアライズ結果に別名を与えるには、DataMember 属性を使います。IgnoreDataMember 属性でシリアライズ対象外になります。

public class AddressRenamedProperty
{
    [DataMember(Name = "Foo")]
    public int Zipcode { get; set; }

    [DataMember(Name = "Bar")]
    public string Address { get; set; }

    [IgnoreDataMember]
    public string TelephoneNumber { get; set; }
}

Utf8Json では dynamic にデシリアライズすることができます。dynamic を使うことでインデクサでのアクセスが可能になります。

デプロイ

Utf8Json

実機で動かすに至らなかったため覚書です。

Unity IL2CPPで動かすには事前にコードジェネレーターを実行する必要があります。
リポジトリからダウンロードした Utf8Json.UniversalCodeGenerator.zip を解凍し、次のコマンドを実行します。

Utf8Json.UniversalCodeGenerator.exe -d "YourTargetDirectories" -o "Utf8JsonGenerated.cs"

作成されたファイルをUnityプロジェクトに移動し、アプリケーションの初期化処理など最初の方に実行されるコードのあたりで Resolver を登録する処理を追加します。

    Utf8Json.Resolvers.CompositeResolver.RegisterAndSetAsDefault(
        Utf8Json.Resolvers.GeneratedResolver.Instance,
        Utf8Json.Resolvers.StandardResolver.Default);
検証しようとしている環境ではGeneratedしたファイルでエラーが出てしまうので暫定対応を取ります。

内容
Utf8Json.Resolvers.DynamicCompositeResolver は抽象クラスのため new できない。

対処
DynamicCompositeResolver.Create メソッドを呼ぶようにし、DynamicCompositeResolverに無理やりキャストして返す。
実機ではここまで処理が到達しなかったのでこれで動くようになるのかはかなり怪しい。
    var ____result = (global::Utf8Json.Resolvers.DynamicCompositeResolver)global::Utf8Json.Resolvers.DynamicCompositeResolver.Create(__formatters__, __resolvers__);

ここまでで立ちはだかるエラーは次の通りです。NotSupportedExceptionとあるように根本的に何かを変えてあげないと動かない予感がします。

NotSupportedException: System.AppDomain::DefineDynamicAssembly
  at System.AppDomain.DefineDynamicAssembly (System.Reflection.AssemblyName name, System.Reflection.Emit.AssemblyBuilderAccess access) [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Internal.Emit.DynamicAssembly..ctor (System.String moduleName) [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.Internal.DynamicObjectResolverAllowPrivateFalseExcludeNullFalseNameMutateOriginal..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.DynamicObjectResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver+InnerResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.StandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at JsonSamples.Start () [0x00000] in <00000000000000000000000000000000>:0 
Rethrow as TypeInitializationException: The type initializer for 'Utf8Json.Resolvers.Internal.DynamicObjectResolverAllowPrivateFalseExcludeNullFalseNameMutateOriginal' threw an exception.
  at Utf8Json.Resolvers.DynamicObjectResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver+InnerResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.StandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at JsonSamples.Start () [0x00000] in <00000000000000000000000000000000>:0 
Rethrow as TypeInitializationException: The type initializer for 'Utf8Json.Resolvers.DynamicObjectResolver' threw an exception.
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver+InnerResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.StandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at JsonSamples.Start () [0x00000] in <00000000000000000000000000000000>:0 
Rethrow as TypeInitializationException: The type initializer for 'Utf8Json.Resolvers.Internal.DefaultStandardResolver.InnerResolver' threw an exception.
  at Utf8Json.Resolvers.Internal.DefaultStandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at Utf8Json.Resolvers.StandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at JsonSamples.Start () [0x00000] in <00000000000000000000000000000000>:0 
Rethrow as TypeInitializationException: The type initializer for 'Utf8Json.Resolvers.Internal.DefaultStandardResolver' threw an exception.
  at Utf8Json.Resolvers.StandardResolver..cctor () [0x00000] in <00000000000000000000000000000000>:0 
  at JsonSamples.Start () [0x00000] in <00000000000000000000000000000000>:0 
Rethrow as TypeInitializationException: The type initializer for 'Utf8Json.Resolvers.StandardResolver' threw an exception.
  at JsonSamples.Start () [0x00000] in <00000000000000000000000000000000>:0 

OnPreBuild

Generator の出力結果がそのままではビルドが通らなかったため、毎回上書きされてもその直後にビルドエラーになることが必須であまり効果的ではありませんでしたが、次の方法でビルドに仕込むこともできます。

すべてをコードで実行することもできますが、次のような Utf8JsonGenerate.bat を用意しました。 バッチは Unity プロジェクトのルートに置いてます。Utf8Json.UniversalCodeGenerator はルートに Utf8json_gen フォルダを作ってそこに一式コピーしてあります。

utf8json_gen\Utf8Json.UniversalCodeGenerator.exe -d "Assets\Scripts" -o "Assets\Scripts\Utf8JsonGenerated.cs"

続いて、Unityのビルド前処理でバッチを実行するように実装します。 これで、Unityをビルドするたびに Utf8Json.UniversalCodeGenerator が実行されます。ただし、最初の一発目は Resolver を登録する必要があるため、ビルド前処理を仕込むよりも先に実行する必要があります。

public class BuildProcessor : IPreprocessBuildWithReport
{
    public int callbackOrder => 0;

    public void OnPreprocessBuild(BuildReport report)
    {
        var info = new ProcessStartInfo();
        info.FileName = "cmd.exe";
        info.Arguments = $"/c " + "Utf8JsonGenerate.bat";
        info.WorkingDirectory = Environment.CurrentDirectory;
        info.WindowStyle = ProcessWindowStyle.Hidden;
        info.UseShellExecute = false;
        info.RedirectStandardOutput = true;

        Process process = null;
        try
        {
            process = Process.Start(info);
            Debug.Log(process.StandardOutput.ReadToEnd());
            process.WaitForExit();
        }
        finally
        {
            if (process != null)
            {
                if (!process.HasExited)
                {
                    process.Kill();
                }
                process.Dispose();
                process = null;
            }
        }
    }
}

Unity ビルドの前後に処理を実行する

ビルド前処理は IPreprocessBuildWithReport 、ビルド後処理は IPostprocessBuildWithReport を実装する。
作成したファイルを Editor フォルダに入れる。

public class BuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
    // 実行される順番
    public int callbackOrder => 0;

    // ビルド前処理
    public void OnPreprocessBuild(BuildReport report)
    {
    }

    // ビルド後処理
    public void OnPostprocessBuild(BuildReport report)
    {
    }
}

ビルド前に外部の exe ファイルを実行する

ビルド前に外部の exe ファイルを実行する場合の実装例。

Process.Start の引数の fileName相対パスでも大丈夫だが、ProcessInfo を使う場合は、ファイル名単体かフルパスじゃないと動かない。

Arguments でパスを使用する場合は、CurrentDirectory = Unity プロジェクトのルートフォルダからの相対パスが使える。

    public void OnPreprocessBuild(BuildReport report)
    {
        var info = new ProcessStartInfo();
        info.FileName = "cmd.exe";
        info.Arguments = $"/c cd";
        info.WorkingDirectory = Environment.CurrentDirectory;
        info.WindowStyle = ProcessWindowStyle.Hidden;
        info.UseShellExecute = false;
        info.RedirectStandardOutput = true;

        Process process = null;
        try
        {
            process = Process.Start(info);

            Debug.Log(process.StandardOutput.ReadToEnd());
            process.WaitForExit();
        }
        catch(Exception e)
        {
            throw new BuildFailedException(e);
        }
        finally
        {
            if (process != null)
            {
                if (!process.HasExited)
                {
                    process.Kill();
                }
                process.Dispose();
                process = null;
            }
        }
    }

HoloLens へアプリを配置する際に「DEP0600」のエラーが出て失敗する場合の解決方法

環境

問題

Visual Studio から HoloLens へアプリを配置しようとした際に、下記のエラーが出て失敗してしまう。

DEP0600: 配置が失敗しました。新しい配置パイプラインを通して配置できませんでした。

f:id:yotiky:20200622082847p:plain

解決方法

  1. HoloLens を再起動する、または Visual Studio を閉じてから HoloLens を再起動する

※現状は様子見