20200212のC#に関する記事は9件です。

Json.NETで、継承関係のあるクラスを可読性を重視しながらシリアライズ/デシリアライズする方法

目的

自作のデータ保持用クラスが複数あり、これらの間に継承関係があるとする。これらのクラスインスタンスをJSON形式にシリアライズ/デシリアライズするため、Json.NETを使用する。

前提

Json.NETは、シンプルなPOCOクラスであれば特別な設定なしにJSON文字列とのシリアライズ/デシリアライズ処理を行ってくれる。また複雑な構造のクラスに対しては、カスタムコンバータに独自のシリアライズ/デシリアライズ処理を定義して利用することもできる。

今回問題となったのは、下記の例のように

  • 自作クラス間に継承の親子関係があり
  • かつ、親クラスが抽象クラスであるなどの理由で、子クラスのインスタンスへデシリアライズしたい
  • しかし(例えば、IList<親クラス>型で子クラスのインスタンスを保持しているため)、Json.NETは親クラスとしてデシリアライズを試みてしまう

場合である。

JsonNetTest0.cs
// 親の抽象クラス
public abstract class Parent
{
    public string Prop0;
}

// 子クラス(具象クラス)
public class Child1 : Parent
{
    public int Prop1;
}

public class Child2 : Parent
{
    public double Prop2;
}

※あくまで例のため簡素なクラスとしていますが、実際にはもう少し複雑なものを想定して書いています。

Program.cs
using Newtonsoft.Json;
using System.Diagnostics;

namespace JsonNetTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var child1 = new Child1() { Prop0 = "Child1", Prop1 = 1234 };
            var child2 = new Child2() { Prop0 = "Child2", Prop2 = 1.234 };

            string child1Json = JsonConvert.SerializeObject(child1);    //"{\"Prop0\":\"Child1\",\"Prop1\":1234}"

            var child1FromJson = JsonConvert.DeserializeObject<Child1>(child1Json);
            Debug.WriteLine($"Child1.Prop0: {child1.Prop0 == child1FromJson.Prop0}");   //Child1.Prop0: True
            Debug.WriteLine($"Child1.Prop1: {child1.Prop1 == child1FromJson.Prop1}");   //Child1.Prop1: True

            string child2Json = JsonConvert.SerializeObject(child2);    //"{\"Prop0\":\"Child2\",\"Prop2\":1.234}"
            var child2FromJson = JsonConvert.DeserializeObject<Child2>(child2Json);
            Debug.WriteLine($"Child2.Prop0: {child2.Prop0 == child2FromJson.Prop0}");   //Child2.Prop0: True
            Debug.WriteLine($"Child2.Prop2: {child2.Prop2 == child2FromJson.Prop2}");   //Child2.Prop2: True
        }
    }
}

ここまでは問題ない。

上記の例ではChild1Child2は共通の親クラスParentを持つ。そこでこれらのインスタンスを、例えばParent[]型の配列でまとめて扱いたくなる場合がある。しかし、

Parent[] parents = new Parent[] { child1, child2 } ;
string parentsJson = JsonConvert.SerializeObject(parents);
Parent[] parentsFromJson = JsonConvert.DeserializeObject<Parent[]>(parentsJson);
// Newtonsoft.Json.JsonSerializationException:
// 'Could not create an instance of type JsonNetTest1.Parent.
// Type is an interface or abstract class and cannot be instantiated.
// Path '[0].Prop1', line 3, position 12.'

このような配列型オブジェクトのデシリアライズはそのままでは行えない。Parentという抽象クラス型の配列の要素を、どの子クラスへデシリアライズすればよいのか判らないからである。

解決法1: TypeNameHandlingを使う

Json.NETでオブジェクトをシリアライズ/デシリアライズする際、オプションにTypeNameHandlingを設定することで、インスタンスの型の情報をJSON文字列に付加することができる(公式ドキュメント)。"$type""$values"というキーが追加され、.NETにおける型の情報が記録される。

適用前

parents(TypeNameHandling.None).json
[
  {
    "Prop1": 1234,
    "Prop0": "Child1"
  },
  {
    "Prop2": 1.234,
    "Prop0": "Child2"
  }
]

適用後

props(TypeNameHandling.All).json
{
  "$type": "JsonNetTest1.Parent[], JsonNetTest",
  "$values": [
    {
      "$type": "JsonNetTest1.Child1, JsonNetTest",
      "Prop1": 1234,
      "Prop0": "Child1"
    },
    {
      "$type": "JsonNetTest1.Child2, JsonNetTest",
      "Prop2": 1.234,
      "Prop0": "Child2"
    }
  ]
}

最初はこの方法を採ったが、例えば上の例だと配列のメンバ1つ1つに$typeキーと型の名前が記述されるため、JSON文字列の情報の密度が薄まるのが気になった。配列の要素数が多いと、全体を確認するのも一苦労である。

とは言えビルトインで使える方法なので、システム外部からJSONを読み込んだりせず1、JSON文字列の可読性や互換性などを気にしないなら、こちらでも何ら問題ない……はず。

解決法2: (本題)可読性を考慮したカスタムコンバータを実装する

こちらが当記事の本題。

まず子クラスへデシリアライズする際の都合上、シリアライズ時に型を判別できる情報はどうしてもJSON文字列に含める必要がある。そこで親クラス専用のカスタムコンバータを定義し、これを使ってコンパクト、かつ型を識別できる書式へシリアライズ/デシリアライズを行ってもらう。

実装例

型の情報を、以下のようにChildTypeキーに保存することにする。

parents.json
[
  {
    "ChildType": 1,
    "Prop0": "Child1",
    "Prop1": 1234
  },
  {
    "ChildType": 2,
    "Prop0": "Child2",
    "Prop2": 1.234
  }
]
Parent.cs
    // 親の抽象クラスの属性に、使用するカスタムコンバータを指定する
    [JsonConverter(typeof(ParentConverter))]
    public abstract class Parent
    {
        public string Prop0;
    }
ParentConverter.cs
// カスタムコンバータの定義
class ParentConverter : JsonConverter
    {
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Parent);
    }

    //シリアライズ処理
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("Prop0");
        writer.WriteValue(((Parent)value).Prop0);

        if (value is Child1 child1) //型を調べて、適切な処理に分岐する
        {
            writer.WritePropertyName("ChildType");
            writer.WriteValue(1);
            writer.WritePropertyName("Prop1");
            writer.WriteValue(child1.Prop1);
        }
        else if (value is Child2 child2)
        {
            writer.WritePropertyName("ChildType");
            writer.WriteValue(2);
            writer.WritePropertyName("Prop2");
            writer.WriteValue(child2.Prop2);
        }
        else
        {
            throw new JsonWriterException();
        }
        writer.WriteEndObject();
    }

    //デシリアライズ処理
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jobject = JObject.Load(reader); //JObject型でJSONの中身を読めるようにする
        switch ((int)jobject["ChildType"])      //ChildTypeの値によって、適切な処理に分岐する
        {
            case 1:
                return new Child1() { Prop0 = (string)jobject["Prop0"], Prop1 = (int)jobject["Prop1"] };
            case 2:
                return new Child2() { Prop0 = (string)jobject["Prop0"], Prop2 = (double)jobject["Prop2"] };
            default:
                throw new JsonReaderException();
        }
    }
}

これで、Child1およびChild2を適切に使い分けてシリアライズ/デシリアライズされるようになった。

問題点

この方法では親クラスのカスタムコンバータに全ての子クラスのシリアライズ/デシリアライズ処理が集約される。子クラスやプロパティが少ないうちは問題にならないが、増えるにつれて1つのカスタムコンバータクラスが肥大化し、可読性・メンテナンス性が低下する。端的に言えば、SRPの原則上良くないように思われる。

解決法2.1: 各子クラス専用のカスタムコンバータを定義し、親クラスのカスタムコンバータから呼び出す

これを解決するために、まず子クラス毎に専用のカスタムコンバータを定義する。上記で親クラス用のコンバータに記述したシリアライズ/デシリアライズ処理を取り出し、子クラス用の方で定義する。

Child1Converter.cs
// 子クラス専用のコンバータ Child2に対しても同様に定義する
class Child1Converter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Child1);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jobject = JObject.Load(reader);
        return new Child1()
        {
            Prop0 = (string)jobject["Prop0"],
            Prop1 = (int)jobject["Prop1"]
        };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var child1Value = (Child1)value;

        writer.WriteStartObject();
        writer.WritePropertyName("ChildType");
        writer.WriteValue(1);
        writer.WritePropertyName("Prop0");
        writer.WriteValue(child1Value.Prop0);
        writer.WritePropertyName("Prop1");
        writer.WriteValue(child1Value.Prop1);
        writer.WriteEndObject();
    }
}

そして親クラス用のカスタムコンバータには、型を判別して子クラスに分岐する処理だけを残す。

ParentConverter.cs
// 親クラスのコンバータ Child1Converter、Child2Converterの処理を呼び出す
class ParentConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Parent);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JsonConverter converter;
        if (value is Child1)
        {
            converter = new Child1Converter();
        }
        else if (value is Child2)
        {
            converter = new Child2Converter();
        }
        else
        {
            throw new JsonReaderException();
        }
        converter.WriteJson(writer, value, serializer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jobject = JObject.Load(reader);
        JsonConverter converter;
        switch ((int)jobject["ChildType"])
        {
            case 1:
                converter = new Child1Converter();
                break;
            case 2:
                converter = new Child2Converter();
                break;
            default:
                throw new JsonReaderException();
        }
        var newReader = jobject.CreateReader();
        return converter.ReadJson(newReader, objectType, existingValue, serializer);
    }

ここで重要なのはReadJsonメソッド、上記の最後から3行目のvar newReader = jobject.CreateReader();ReadJsonメソッドの引数をそのまま子クラスのReadJsonに渡したくなるが、引数のうちJsonReader readerだけはそのまま渡すことができない。
ドキュメントによればJsonReaderは"forward-only access"なものであり、おそらく一度しか使えない仕様なのだろう。ReadJsonメソッドの冒頭でJObject jobject = JObject.Load(reader);とした時点で"使われて"いるため、子クラスのReadJsonメソッドに渡して再び使おうとするとJsonReaderExceptionが発生する。
そこで、jobjectから未使用のJsonReaderを作り直して渡している。

またついでに、以下のように子クラスのJsonConverter属性を明示的に指定しておくと、子クラスを直接シリアライズ/デシリアライズする時、親クラスの方を経由せずに直接呼び出してくれる……はず。

Child1.cs
[JsonConverter(typeof(Child1Converter))]
public class Child1 : Parent
{
    public int Prop1;
}

(ただ指定しなくても、Json.NETは親クラスのJsonConverter属性まで遡って調べるらしく、最終的には正しいカスタムコンバータを呼んできてくれる。)

解決法2.2: 親クラスのカスタムコンバータのWriteJsonを省略する

更に試行錯誤を重ねたところ、今回のユースケースではどうやらParentConverterのWriteJsonメソッドは不要らしいことが判った。Json.Netの方でParent[]の各要素の型を自動的に判別して、適切な子クラス用のコンバータを呼び出してくれている……らしい。というわけで、

ParentConverter.cs
// 親クラスのコンバータ Child1Converter、Child2Converterの処理を呼び出す
class ParentConverter : JsonConverter
{
        public override bool CanWrite => false;
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
        (以下略)

こうなった。

元々はこういう感じに、子クラスのJsonConverter属性を読みに行き、対応するカスタムコンバータを探してWriteJsonを呼び出す方法を試していた。一応、動作することは確認できている。

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JsonConverterAttribute jca = (JsonConverterAttribute)Attribute.GetCustomAttribute(value.GetType(), typeof(JsonConverterAttribute));
    object instance = Activator.CreateInstance(jca.ConverterType, false);
    jca.ConverterType.GetMethod("WriteJson").Invoke(instance, new object[] { writer, value, serializer });
}

まとめ

継承関係のある親・子クラスに対してJson.NETのカスタムコンバータを定義し、1つの子クラスのシリアライズ/デシリアライズ処理を対応する1つのクラス内で完結させ、かつ親クラスをデシリアライズできるようにした。実装に当たっては出力されるJSON、および入出力を行うカスタムコンバータクラスの可読性に配慮した形とした。

これに限らず、もっと良さげな方法があればぜひご教示下さい。

最終的なソースコード

表示する
JsonNetTest2_2.cs
using System;
using System.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonNetTest2_2
{
    // 親の抽象クラス
    [JsonConverter(typeof(ParentConverter))]
    public abstract class Parent
    {
        public string Prop0;
    }

    // 子クラス(具象クラス) 対応するカスタムコンバータを指定している
    [JsonConverter(typeof(Child1Converter))]
    public class Child1 : Parent
    {
        public int Prop1;
    }

    [JsonConverter(typeof(Child2Converter))]
    public class Child2 : Parent
    {
        public double Prop2;
    }


    class Child1Converter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Child1);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jobject = JObject.Load(reader);
            return new Child1() { Prop0 = (string)jobject["Prop0"], Prop1 = (int)jobject["Prop1"] };
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var child1Value = (Child1)value;

            writer.WriteStartObject();
            writer.WritePropertyName("ChildType");
            writer.WriteValue(1);
            writer.WritePropertyName("Prop0");
            writer.WriteValue(child1Value.Prop0);
            writer.WritePropertyName("Prop1");
            writer.WriteValue(child1Value.Prop1);
            writer.WriteEndObject();
        }
    }

    class Child2Converter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Child2);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jobject = JObject.Load(reader);
            return new Child2() { Prop0 = (string)jobject["Prop0"], Prop2 = (double)jobject["Prop2"] };
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var child1Value = (Child2)value;

            writer.WriteStartObject();
            writer.WritePropertyName("ChildType");
            writer.WriteValue(2);
            writer.WritePropertyName("Prop0");
            writer.WriteValue(child1Value.Prop0);
            writer.WritePropertyName("Prop2");
            writer.WriteValue(child1Value.Prop2);
            writer.WriteEndObject();
        }
    }

    class ParentConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Parent);
        }
        public override bool CanWrite => false;
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jobject = JObject.Load(reader);
            JsonConverter converter;
            switch ((int)jobject["ChildType"])
            {
                case 1:
                    converter = new Child1Converter();
                    break;
                case 2:
                    converter = new Child2Converter();
                    break;
                default:
                    throw new JsonReaderException();
            }
            var newReader = jobject.CreateReader(); //As JsonReader cannot be used twice, create new one and pass it.
            return converter.ReadJson(newReader, objectType, existingValue, serializer);
        }
    }
}
Program.cs
using Newtonsoft.Json;
using System.Diagnostics;

using JsonNetTest2_1;

namespace JsonNetTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var child1 = new Child1() { Prop0 = "Child1", Prop1 = 1234 };
            var child2 = new Child2() { Prop0 = "Child2", Prop2 = 1.234 };
            string child1Json = JsonConvert.SerializeObject(child1);
            var child1FromJson = JsonConvert.DeserializeObject<Child1>(child1Json);
            Debug.WriteLine($"Child1.Prop0: {child1.Prop0 == child1FromJson.Prop0}");
            Debug.WriteLine($"Child1.Prop1: {child1.Prop1 == child1FromJson.Prop1}");

            string child2Json = JsonConvert.SerializeObject((Parent)child2);
            var child2FromJson = JsonConvert.DeserializeObject<Child2>(child2Json);
            Debug.WriteLine($"Child2.Prop0: {child2.Prop0 == child2FromJson.Prop0}");
            Debug.WriteLine($"Child2.Prop2: {child2.Prop2 == child2FromJson.Prop2}");

            Parent[] parents = new Parent[] { child1, child2 } ;
            var jss = new JsonSerializerSettings() { Formatting = Formatting.Indented};
            string parentsJson = JsonConvert.SerializeObject(parents, jss);
            Parent[] parentsFromJson = JsonConvert.DeserializeObject<Parent[]>(parentsJson, jss);
            foreach (var child in parentsFromJson)
            {
                Debug.WriteLine(JsonConvert.SerializeObject(child));
            }
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

制約の多い運用現場でエンジニアっぽいことをする方法

対象

  • セキュリティの関係でソフトやツールをインストール・導入できない
  • お金がないからツールを購入してもらえない
  • サクラエディタでコードを書いてる
  • そもそも周りにコードかいてるやつがいない
  • IEが推奨ブラウザ(そもそも外部にアクセスできない)
  • 何でもかんでもExcelでやりたがるetc.

上記はほとんど自分が今の現場に感じている不満だがおそらく自分だけじゃないはず.今や自宅での環境のほうが優れている人がほとんどだと思う.WEB系で働いている人のツイートとかみるとめっちゃ羨ましく思う.でもWindowsにはメモ帳があるじゃないか.

PowerShell

用途としては単純作業の自動化がメイン

  • メリット

    • 個人的に最近ハマっている
    • Windowsに標準でインストールされている
    • PowerShell ISEという統合開発環境がある
    • .NET Frameworkを利用できる
    • COMオブジェクトの操作が可能
    • CSV,XML,JSONを扱いやすい(これで少しは脱Excelできるかも)
    • レスポンスがLinuxのシェルと違って文字列ではなく,オブジェクトで返ってくるので扱いやすい
  • デメリット

    • ユーザが少ないので情報も少ない
    • 実行ポリシーが存在していてバッチファイルみたいにダブルクリックしてすぐ実行とはならない

HTML/CSS/JavaScript/VBScript

静的なポータルサイトとかアプリケーション

  • HTA(html Applications)でアプリ化できる
    • IEがあれば実行できる
    • AxtiveXが使える
  • ファイルサーバで静的サイトみたいに運用できる

C#

PowerShellの上位互換という感じでアプリとかちょっとしたスクリプトも可能

  • .NET Frameworkを利用できる
  • コマンドプロンプトでコンパイルできる(VisualStudioなくても開発可)
  • 細かいとこまろで作りこむことができる

まとめ

世の中にはもっと便利なものがたくさんあるが,基本的にWindowsで標準で入ってるものをうまく利用していくしかない.あのライブラリ使えたら楽だなーとかおもうこともあるが,利用できる環境じゃなければ自分で作っていくしかない.車輪の再発明かもしれないけど,この経験は無駄にはならないと思うので同じような境遇の方には是非試してもらいたい.またこの他に使えるものがあればぜひ教えてくださいー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# で Brain Fuck のインタプリタ作って Nuget に公開した

なんでそんなもん作ったの?

大昔にこんなものを作りました。

響ちゃんマジ天使言語

何かというと、Brain Fuck の各トークンをアイマスの我那覇響ちゃんへの愛を叫ぶ言葉に置換して、コーディングできるようにしたものです。
ずいぶん前に話題になったおっぱい言語みたいなやつですね。

で、これ自体は WinForms のコードビハインドにゴリゴリ実装した、「MVVM? WPF? なにそれ、おいしいの?」状態のなかなかなシロモノです。
そんなものなので、ずっとリファインしたかったんです。

また、当時から時が経ち、 .Net Core がリリースされて、 C# でマルチプラットフォームに対応するのが劇的に簡単になりました。

というわけで、リファインするにあたって、次のような目標を立てました。

  • 解析する部分はクラスライブラリとして外に出す
  • フレームワークは .Net Core としてマルチプラットフォームで実行できるようにする
  • トークンの定義を設定ファイルに切り出し、ユーザが好きにカスタマイズできるようにする

BFCore 爆誕

そして、出来上がったものがこちら。(キュー〇ー3分クッキング)

ソースコードはこちら
Nuget はこちら

上記の目標は一応達成できました。
ちなみに、フレームワークは .Net Core から .Net Standard 1.3 まで落としました。

サンプルアプリとして、 BFCore を使用してリファインした響ちゃんマジ天使言語もあります。
まあ、笑ってやってください。

実装に関してはそんなに工夫してないというか、まあ、ごり押しに近いです。
プルリクエストなど頂ければ泣いて喜びます。

使い勝手がいいかはあまりわかりません。
というか、他の人の感想が知りたくて、宣伝のために記事を書いたようなものだったり……

使い方に関しては、 GitHub の README に詳しく書いているので、そちらを参考にしてください。

さいごに

内容がめちゃくちゃ薄くて申し訳ありません。
直接は関係ありませんが、 GitHub の Actions で master にマージしたら自動で Nuget に公開されるようにしました。
たぶん、こっちのほうが需要が高い気がするので、そちらについてまたあとで記事にしたいと思います。

それでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# で Brainfuck のインタプリタ作って Nuget に公開した

なんでそんなもん作ったの?

大昔にこんなものを作りました。

響ちゃんマジ天使言語

何かというと、Brainfuck の各トークンをアイマスの我那覇響ちゃんへの愛を叫ぶ言葉に置換して、コーディングできるようにしたものです。
ずいぶん前に話題になったおっぱい言語みたいなやつですね。

で、これ自体は WinForms のコードビハインドにゴリゴリ実装した、「MVVM? WPF? なにそれ、おいしいの?」状態のなかなかなシロモノです。
そんなものなので、ずっとリファインしたかったんです。

また、当時から時が経ち、 .Net Core がリリースされて、 C# でマルチプラットフォームに対応するのが劇的に簡単になりました。

というわけで、リファインするにあたって、次のような目標を立てました。

  • 解析する部分はクラスライブラリとして外に出す
  • フレームワークは .Net Core としてマルチプラットフォームで実行できるようにする
  • トークンの定義を設定ファイルに切り出し、ユーザが好きにカスタマイズできるようにする

BFCore 爆誕

そして、出来上がったものがこちら。(キュー〇ー3分クッキング)

ソースコードはこちら
Nuget はこちら

上記の目標は一応達成できました。
ちなみに、フレームワークは .Net Core から .Net Standard 1.3 まで落としました。

サンプルアプリとして、 BFCore を使用してリファインした響ちゃんマジ天使言語もあります。
まあ、笑ってやってください。

実装に関してはそんなに工夫してないというか、まあ、ごり押しに近いです。
プルリクエストなど頂ければ泣いて喜びます。

使い勝手がいいかはあまりわかりません。
というか、他の人の感想が知りたくて、宣伝のために記事を書いたようなものだったり……

使い方に関しては、 GitHub の README に詳しく書いているので、そちらを参考にしてください。

さいごに

内容がめちゃくちゃ薄くて申し訳ありません。
直接は関係ありませんが、 GitHub の Actions で master にマージしたら自動で Nuget に公開されるようにしました。
たぶん、こっちのほうが需要が高い気がするので、そちらについてまたあとで記事にしたいと思います。

それでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でメモ化を実装してみた

C#でデコレーター風味でメモ化を実装してみました。
デコレーターをC#で実装しようとやっていた際に思いついたのでここに置いておきます。

メモ化とは

重い計算をする際によく用いられます。
同じ引数だと同様の結果を返すような場合、二回目も三回目も同様に計算を行うのは処理時間の都合上非常に無駄です。
なので、引数と戻り値のリストを作成しておいてリストにある引数が渡された場合計算をせずにリストから結果を渡すというものです。

実装方法

Dictionaryで計算結果を保持して引数が存在するかの比較を行います。
今回は以下のように実装しました。
計算を行うメソッドはこのように記述しました。

public long Decorate_square (long x) {
    return x * x;
}

今回は簡単に二乗を行うメソッドです。
そして実行を代行するメソッドはこちらです。

public long square (long sx2) {
    Func<long, long> f = Decorate_square;
    Decorator.memo<long, long> memo = new Decorator.memo<long, long> ();
    return memo.Invoke (data, f, sx2);
}

Funcでメソッドを変数にいれてメモ化のメソッドの型を指定してメモ化のメソッド経由で実行します。
そして、メモ化を行うメソッドはこのように実装しました。

using System;
using System.Collections.Generic;
using System.Reflection;
public class Decorator {
    public Dictionary<object,object> data = new System.Collections.Generic.Dictionary<object,object>();
    public class memo<arg01, ans> {
        public ans Invoke (Dictionary<object,object> data, Func<arg01, ans> f, object x) {
            if (data.ContainsKey((arg01)x)) {
                Console.WriteLine ("-O-");
                return (ans) data[(arg01)x];
            } else {
                Console.WriteLine ("-X-");
                ans res = (ans) (object) f ((arg01) x);
                data[(arg01)x] = res;
                return res;
            }
        }
    }
}

これらを書くのは面倒では。。。

はい、その通りです。
そこでRoslynのVisualStudio拡張機能の修正機能です。
私の別の記事で書きましたがこれはソースコード自体を自動で書き換えることができます。
なので、[memo]などと記述した際に自動でデコレーターメソッドを記述できるようになると非常に良いです。

全コード

generic.cs
using System;
using System.Collections.Generic;
using System.Reflection;

public class deco2 : Decorator {
    public long square (long sx2) {
        Func<long, long> f = Decorate_square;
        Decorator.memo<long, long> memo = new Decorator.memo<long, long> ();
        return memo.Invoke (data, f, sx2);
    }

    public long Decorate_square (long x) {
        return x * x;
    }
}

memo_decorator.cs
using System;
using System.Collections.Generic;
using System.Reflection;

public class Decorator {
    public Dictionary<object,object> data = new System.Collections.Generic.Dictionary<object,object>();
    public class memo<arg01, ans> {
        public ans Invoke (Dictionary<object,object> data, Func<arg01, ans> f, object x) {
            if (data.ContainsKey((arg01)x)) {
                Console.WriteLine ("-O-");
                return (ans) data[(arg01)x];
            } else {
                Console.WriteLine ("-X-");
                ans res = (ans) (object) f ((arg01) x);
                data[(arg01)x] = res;
                return res;
            }
        }
    }
}

以下は実行部分です

Program.cs
namespace twice_arg_memo {
    class Program {
        static void Main (string[] args) {
            deco2 d = new deco2 ();
            Console.WriteLine (d.square (2));
            Console.WriteLine (d.square (2));
            Console.WriteLine (d.square (3));
            Console.WriteLine (d.square (4));
            Environment.Exit (0);
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# 最大公約数と最小公倍数

競技プログラミングの問題を解いている際に、最小公倍数か最大公約数を求める必要に迫られる場合がある。
今までは、先人らのコードをコピペして使わせて頂いていたのだが、モラル的に良くない気がしたので自分用に書いたため、貼っておく。
型などlongの方が良いかも。
最大公約数はユークリッドの互除法より得られ、また最小公倍数は元の2数をかけて最大公約数で割ったものから得られる。

9,18の場合...最大公約数は9,これらをかけて9で割るので18が最小公倍数となる。

// 最大公約数
public static int Gcd(int a, int b)
    {
        var x = 0;

        while (true)
        {
            // 割り切れたら
            if (a % b == 0)
            {
                return b;
            }
            else
            {
                x = a % b;
                a = b;
                b = x;

            }
        }

    }
// 最小公倍数
    public static int Lcm(int a, int b)
    {
        var x = Gcd(a, b);

        return a * b / x;
    }

呼び出し方としては、こんな感じで(適当にMain関数の中で呼んだけど)指定してあげてね。

using System;

    public static void Main()
    {

      var a = 10;
      var b = 20;
      Console.WriteLine(Gcd(a,b));
      Console.WriteLine(Lcm(a,b));

    }

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「UnityのARFoundationでAR空間に豆腐を召喚する」をやって、ARFoundationをフレームワークとして使う上で困らないレベルに理解してみた

はじめに

ARFoundationを利用したオリジナルARアプリを作成するにあたり、@shun-shun123さんのQiita記事にあった「UnityのARFoundationでAR空間に豆腐を召喚する」を練習でやってみた。
元記事URL:https://qiita.com/shun-shun123/items/1aa646049474d0e244be

今回は、上記記事を通じて「ARFoundationをフレームワークとして使う上で困らないレベルに理解してみた」内容を書いていこうと思う。(ほぼ公式サイトを和訳しただけ)
※ARFoundationで提供されるスクリプトのコード理解も後でやりたい
Unity公式サイト ARFoundationページ:https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@3.0/manual/index.html#basic-setup

環境

Unity 2019.3.0f6
ARFoundation 3.0.1
AR Subsystems 3.0.0
ARKit XR Plugin 3.0.1
XR Legacy Input Helpers 1.3.8
Xcode 11.3.1
macOS Catalina 10.15.2
iOS 13.2.3

基本設定

まずはじめにSceneに「AR Session」と「AR Session Origin」をGameObjectとして追加する。
2020-02-12 13.08のイメージ.jpeg
2020-02-12 13.07のイメージ.jpeg
※画像はUnity公式サイトより

この2つのObjectはAR機能を実装する上で必須らしい。
それぞれが果たしている役割が公式サイトに書いてあったので読んでみた。

AR Session

・一連のAR体験をコントロールする(セッション機能)
・ターゲットのプラットフォーム(実機等)でAR機能を利用可能/不能にする

AR Session Origin

・検知したObjectの特徴(位置・向き・大きさ)を確定させる

AR体験においては、「現実世界で検知したものをデジタルなGameObjectとして認識」or「現実世界にデジタルなGameObjectを生成」のように、カメラ越しにリアルとデジタルが重畳表示される。
その際に、例えばカメラが移動してもセッション開始時の原点を覚えておくことで、現実世界で適切にGameObjectの位置・向き・大きさを表示することができる。

このAR Session Originが果たす役割上(現実世界で適切にGameObjectの位置・向き・大きさを表示)、「現実世界を認識するCamera Object」&「現実世界に生成させるGame Object」は、AR Session Originの子Objectでなければならない。

2020-02-12 13.07のイメージ.jpeg
※AR CameraがAR Session Originの子Objectになっている。
※今回「現実世界に生成させるGameObject」である豆腐は、豆腐PrefabのジェネレータスクリプトをAR Session Originのコンポーネントにしている。

AR Camera

初期設定から自分で変更した点があったので、ついでにAR Cameraについても書いておく。
上述の通り、AR Session Originの子Objectに配置されるAR Cameraだが、下記3つのコンポーネントを有している。

① AR Camera Manager
・実機カメラのAuto Focusモードのオン/オフ
・現実世界の光量計算機能のオン/オフ(パフォーマンスに大きく影響するので基本はオフに設定)
2020-02-12 14.36のイメージ.jpeg
※画像はUnity公式サイトより

② AR Camera Background
・実機カメラの映像を背景として表示させる(自分で背景映像をカスタム可能だが、ARアプリなら基本的にはこのまま使う)
2020-02-12 14.37のイメージ.jpeg
※画像はUnity公式サイトより

③Tracked Pose Driver
・実機カメラの位置と向きを認識する(実機から位置・向き情報を読み出す)
2020-02-12 14.50のイメージ.jpeg
※画像はUnity公式サイトより

公式サイトには、「AR Cameraには上記3つのコンポーネントがある」と書いてあったのだが、私がAR Session OriginをSceneに追加したときには、Tracked Pose Driverではなく、AR Pose Driverとなっていた。
2020-02-12 14.56のイメージ.jpeg

後々、不具合が発生してもめんどくさいので、公式サイトに則ることにし、AR Pose DriverをDelete、Tracked Pose DriverをAddしておいた。
※AR Pose DriverとTracked Pose Driverの違いは、後ほどコードを紐解きつつ理解したい。

平面検知 & 検知した平面に豆腐召喚

豆腐を召喚させる方法として、元記事ではまず平面検知をしていた。
平面検知の実装方法としては、AR Plane Managerを、GameObjectのAR Session Originにコンポーネントとして追加するだけで完了。
(厳密には、AR Plane Managerを、ScriptのAR Session Originと同様のGameObjectに追加していればOK)

また「画面タップしてRay(光線)を飛ばし、その光線と検知した平面が衝突した点に豆腐を召喚」という機能を実装するため、AR Raycast ManagerをAR Session Originにコンポーネントとして追加。
最後にPlace On Plane(これは自分で1から書くScript)もAR Session Originにコンポーネントとして追加。
※この辺の実装方法・コードは元記事を見ていただきたい。
※元記事URL:

ちなみにAR Session OriginのInspectorは最終的にこんな感じになった。
2020-02-12 17.03のイメージ.jpeg

これで実装完了なのだが、最後の最後に戸惑った部分があったので共有しておく。
実際に実機(iPhone11 Pro)で動かしてみたときに、検知した平面をタップしても、豆腐が召喚されないという現象が起きた。エラーは何も見当たらないのに。
原因は、召喚した豆腐がデカすぎてカメラが豆腐の内側に入ってしまったため、召喚されていないように見えていただけだった。
TofuPrefabのScaleをX・Y・Zすべて 1→0.1 に変更して解決。

Trackable Manager

AR Plane Managerは、ARFoundationで提供されるTrackable Managersのひとつで、Trackable ManagersはScriptのAR Session Originと同様のGameObjectに追加される必要がある。
理由は、前述した通り、AR Session Originが果たす役割である(現実世界で適切にGameObjectの位置・向き・大きさを表示)。

2020-02-12 17.20のイメージ.jpeg
※画像はUnity公式サイトより
※ちなみにTrackable Managersは上記表の種類がある。
今回、平面検知に使用したAR Plane Manager だけでなく、例えばAR Tracked Image Managerを使用すれば人体検知ができたりする。

Trackable Managersは、各々が検知可能な現実世界の物体が定められており(あるいは自ら定義することも可能っぽい)、その物体検知に基づき、GameObjectの生成・削除を常にupdateしている。

最後に

ARFoundationをフレームワークとして使える状態になるために、この記事を書いてみたが、人に説明できるまで理解するのはやっぱり難しい、、(まだコードの紐解きはできていないし、、、泣)

用語の使い方がおかしかったり、公式サイトの英語をニュアンスで理解してしまっている部分もあるので、間違っている部分があればご指摘いただけると幸いです。

あと、これまで新しいフレームワークを勉強するときは日本語のサイト・記事でどうにかしようとしてばかりいたけど、ARFoundationに関しては公式サイトが一番体系的で分かりやすかった。
英語から逃げてたらダメだ、、!!

Unity公式サイト AR Foundationページ:https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@3.0/manual/index.html

初めて知った用語たち

offset
https://wa3.i-3-i.info/word11923.html
→位置を基準点からの距離で表すこと

session
https://it-trend.jp/words/session
→接続を確立してから切断するまでの一連の通信のこと。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【WPF】ListBoxItemのインデックスをContextMenuのCommandParameterにBindingする方法

ListBoxのインデックス

ListBoxのインデックスを取得する方法

ListBoxのインデックスはListBoxAlternationCountプロパティとAlternationIndex添付プロパティの組み合わせで取得できます。

{Binding Path="(ItemsControl.AlternationIndex)" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}"}

参考:
https://qiita.com/tera1707/items/791bdb887eae4c0ea6a4

ContextMenuでの対応

しかし、ContextMenuではツリーが別になるためPlacementTargetなどを駆使してDataContextなどを受け渡す必要があります。

そうするとListBoxItemを取得できるのはContextMenu.PlacementTargetになりますが、PlacementTarget.(ItemsControl.AlternationIndex)とすることでAlternationIndex添付プロパティが取得できます。

<ListBox
    ItemsSource="{Binding Items}"
    AlternationCount="1000">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter
                Property="Tag"
                Value="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}"/>
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                        <MenuItem
                            Header="{x:Static properties:Resources.Delete}"
                            Command="{Binding RemoveCommand}"
                            CommandParameter="{Binding PlacementTarget.(ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType=ContextMenu}}"
                            />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#で不定形JSONを自在に扱いたい

C#でJSONを扱う記事は探せばたくさん出てくるのですが、そのほとんどが
・一段階のフラットJSONしか扱ってない
・先に展開先クラスを用意する方法
のどちらかしか扱っていません。

それらの方法では私のやりたいことができなかったので、そこらへんについて調べた記録です。

Microsoft公式のSystem.Text.Jsonを使ってJSONを扱うためには、先に展開したいJSONと相似形のクラスを用意しておく必要があります。

以下では公式のJSONサンプルを例に、操作を行ってみます。

    // 展開する先のクラス構造
    public class WeatherForecastWithPOCOs
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string Summary { get; set; }
        public string SummaryField;
        public IList<DateTimeOffset> DatesAvailable { get; set; }
        public Dictionary<string, HighLowTemps> TemperatureRanges { get; set; }
        public string[] SummaryWords { get; set; }
    }
    public class HighLowTemps
    {
        public int High { get; set; }
        public int Low { get; set; }
    }

    // JSON文字列の例
    string jsonString = "{\"Date\":\"2019-08-01T00:00:00-07:00\",\"TemperatureCelsius\":25,\"Summary\":\"Hot\",\"DatesAvailable\":[\"2019-08-01T00:00:00-07:00\",\"2019-08-02T00:00:00-07:00\"],\"TemperatureRanges\":{\"Cold\":{\"High\":20,\"Low\":-10},\"Hot\":{\"High\":60,\"Low\":20}},\"SummaryWords\":[\"Cool\",\"Windy\",\"Humid\"]}";

    // デシリアライズ
    var weatherForecast = JsonConvert.DeserializeObject<WeatherForecastWithPOCOs>(jsonString);

めんどくさすぎる…

何が困るって、APIとかの外部からやってくる不定形のJSONをパースしたいのですよ。
APIなんて形がすぐ変化するので、先に形式を用意しておかないといけないSystem.Text.Jsonは全くもってこの世界には向いていません。
もっとこう$obj = json_decode($jsonString)みたいなのはないんですかね。

実のところSystem.Text.Jsonはかなり機能が少ないです。
このような用途にはNewtonsoft.Jsonのほうが向いています。

    // Newtonsoft.Jsonを使う
    var weatherForecast = JsonConvert.DeserializeObject(jsonString);

こうするとクラスを用意しておく必要がなくなるのですが、返ってくるのはobjectなのでいまいち役に立ちません。
全部Dictionaryあたりにしてほしいのですが。

…普通にNewtonsoft.Json公式にあったわ。

    Dictionary<string, string> weatherForecast = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonString);

やったか!
と思いきやDatesAvailableあたりはstringじゃないので当然ながら死にます。
パース方法はDataSetとかImmutableListとか色々ありますが、どれも全ての値が同じ型で、段階がフラットであることを前提としたものです。

中身が入れ子になってたりなってなかったりするJSONを、全部連想配列か何か展開してほしいのですよ。
Dictionary<string, typeof(Value)>みたいに書けないものですかね。

それからなんやかんやあって、最終的にJObjectを使ってどうにかすることができました。

    // デシリアライズ
    JObject weatherForecast = JObject.Parse(jsonString);

    // 直下の値を取得
    Console.WriteLine("Date: " + weatherForecast["Date"].ToString());

    // 入れ子の値を取得
    Console.WriteLine("TemperatureRanges_Cold_High: " + weatherForecast["TemperatureRanges"]["Cold"]["High"].ToString());

    // []
    var DatesAvailable = weatherForecast["DatesAvailable"].Children();
    foreach (var DatesAvailabl in DatesAvailable)
    {
        Console.WriteLine("DatesAvailable: " + DatesAvailabl.ToString());
    }

    // {}
    JEnumerable<JToken> TemperatureRanges = weatherForecast["TemperatureRanges"].Children();
    foreach (JToken TemperatureRange in TemperatureRanges)
    {
        // キー
        Console.WriteLine("TemperatureRange Key: " + ((JProperty)TemperatureRange).Name);
        // 値
        Console.WriteLine("TemperatureRange Value: " + TemperatureRange.First["High"]);
        // 一部だけ定型クラスに取り出したい
        HighLowTemps highLowTemps = TemperatureRange.First.ToObject<HighLowTemps>();
    }

    // トップレベルに追加
    weatherForecast["hoge"] = "fuga";

    // 削除
    weatherForecast.Property("Summary").Remove();

    // []に追加
    ((JArray)weatherForecast["DatesAvailable"]).Add("2020-02-02T02:02:02-02:00");

    // {}に追加
    ((JObject)weatherForecast["TemperatureRanges"]).Last.AddAfterSelf(
        new JProperty("Lukewarm", JObject.Parse(@"{""High"": 10,""Low"": 10}"))
    );
    // これでもいい
    weatherForecast["TemperatureRanges"]["Absolute"] = JObject.Parse(@"{""High"": -273,""Low"": -273}");

    // JSON文字列に戻す
    var jsonStringAfter = JsonConvert.SerializeObject(weatherForecast);

とりあえずこれで、C#でJSONをそこそこ自在に操ることができるようになったと思います。
PHPならどれも一瞬でできるのに……とか思いつつここまで調べるのに半日かかった。

なおサンプルはそのままだと.ToList()のあたりでエラーになるので、どこかで仕様が変わったようです。

このくらいの記事ってPHPなら腐るほど転がってると思うのですが、C#だとMicrosoftのドキュメントか、ほとんど公式のコピペのような内容しか見つからないんですよね。
それらを応用してどうこうするって内容がどうにもなかなか存在しない。
独学でレベルアップするには非常に厳しい言語だと思います。

いや、この程度書くほどでもない常識だからどこにも書いてないんだよ、とか言われたら死にます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む