情シスを保守的だと誤解していた話

こんにちは!弥生のいっしーです。
この記事は、Misoca+弥生 Advent Calendar 2019 の19日目の記事です。
いよいよカレンダーも終盤ですね!

年末なので、ふりかえりも兼ねて、2月から所属している情報システム部での気付きなどを書いていこうと思います。

これまでのキャリア

デスクトップ製品が共通で使う機能の開発

デスクトップ製品のテスト自動化

マイポータルWebの開発(主にマネジメントや品質管理)

Misocaの開発

弥生会計 オンラインの開発

新課金システムの開発 ← New!

2013年に新卒で入社し、途中まではプロジェクトマネージャー志望でした。
が、もう少し自分で手を動かしたくなり、クラウドサービスの開発へ。

さらなる修行を、ということで、これまであまり経験のなかった社内運用までを含めた開発を行う新課金チーム(情シス)に至っています。

新課金チームでは、弥生のサービスの契約販売管理システムを旧システムから移行するべく、新しい画面を作ったり、バッチ処理を組んだり、契約まわりの問い合わせ対応を主に行っています。

情シスに対する認識の変化

配属されるまでの私の情シスイメージは

  • 社内システムやインフラの監視やメンテナンスをしている部署
  • 社内のIT関連で困ったときの一次受け
  • あんまり開発してなさそう

くらいの認識で、お世話になっているけれども、正直なところ何をやっているのかよく分かっていませんでした。

ごめんなさい。

ただ、近年はカスタマーセンター用にチャットボットの導入やRPAによる自動化など、 新しいことに取り組み始めている雰囲気も感じていました。
配属されてからは「開発:運用=7:3」くらいの割合で業務を行っています。

10ヶ月経過したいまは、情シスは

  • いろいろな部署やシステムのハブといっても過言ではない部署
  • 結構、開発もできる
  • 社内業務の効率化を継続して提案・実現していければ、最強の弥生を作り上げられるのでは

と考えています。

情シス(というか新課金システム業務)のここが○○!

魅力 ~社内の各業務を支えている、担っていると実感できる~

マーケティング本部から「こんなキャンペーンを打ちたいからプランの枠を用意してほしい」といった要望をうけたり、 財務経理部へ毎月の売上状況の資料提供を行ったりするなど、ビジネス施策や社内の重要業務に対応する必要があります。

プロダクト開発をしていた頃にはあまり認識していなかった他部署の個別具体的な業務が見えるようになり、 自分の業務がその役に立っていることをタイムリーかつダイレクトに知ることができます。

相手の顔が見えた方が頑張れる人は、意外と情シスが向いているかもしれません。

おもしろい ~過去に自分が関わった機能をいまは呼び出す側~

新課金システムはマイポータルWebやMisocaとも連携するため、過去に自分が開発した機能やテストをした機能を呼び出すことがあります。

そんなとき、自分の中で点と点がつながる感じがして面白く、また感慨深いです。

こわい ~本番環境、めっちゃこわい~

リリース等で本番稼働中のサーバーを操作したり、依頼などでデータをメンテナンスしたりするのですが、関連するシステムが多いため、他に影響が出ないかビクビクしています。1

契約まわりはセンシティブな部分なので、手順書と実際に実行するSQL等は有識者レビューを行い、指さし確認しながら実施するなど、細心の注意を払っています。

これから

いろいろと書きましたが、新課金システムすらまだまだ把握しきれていないので、そこを強化しつつ、 関連システムとの関係性を理解し、旧システムからの移行を最後までやり遂げたいです。

また、情シスはいいぞ、という普及活動をして仲間を増やせれば、と考えています。

さいごに①:次回予告

明日20日の Misoca+弥生 Advent Calendar 2019 は、消防士からエンジニアという異色の経歴をもつ ryo2132 さんです!

さいごに②:宣伝

本日19日は2019年最後のもくテク2があります!

mokuteku.connpass.com

課金システムの話や軽減税率の話など、盛りだくさんの内容となっています。
ぜひご参加ください!


  1. 本番環境でやらかしちゃった人 Advent Calender 2019 も明日は我が身…!と思いながら読んでいます。

  2. 弥生が開催しているIT勉強会。木曜日にテクノロジーを語ろう、の略。

Microsoft AzureのOCRサービスについて

この記事はMisoca+弥生 Advent Calendar 2019 - Qiitaの17日目のエントリーになります。

こんにちは、弥生のつじのです。
普段は弥生のインフラチームで商用環境の構築・運用などを主に担当しています。

私はインフラチーム内でMicrosoft Azure(以下Azure)に触れる機会が比較的多いので、Azureに絡めた話題としてAzureのOCRサービスについて紹介したいと思います。

■OCRサービスって?

Optical Character Readerの略です。
画像データ内の文字を認識してtxt、csvなど文字列データへ変換する装置のことです。

■リソースの紹介

今回Azureで使用する主なリソースについてです。

Azure Logic Apps docs.microsoft.com Azure内部の操作を自動実施したり、外部からのトリガーで特定の操作が可能なリソースです。 GUI上からある程度直感的に処理の作成が可能です。

OCR 認知スキル docs.microsoft.com Azureが提供しているOCRサービスです。 このサービスに画像データを送り込むことで、その画像データからテキストデータを取得します。

■動作環境準備

今回はAzure上に以下の様な環境を構築します。
※構成手順は省略

f:id:ym_AdventC:20191217093656p:plain
構成概要

①:手動で画像データをアップロード
②:画像データの有無を定期的にチェック
③:画像データをOCRサービスへ読み込ませる
④:OCR結果をtxtファイルに出力
⑤:手動で出力結果をダウンロード

■アップロードと精度検証

次はAzureのOCRの精度検証です。
アップロードする画像はアドベントカレンダーなので、冬らしい和歌を調べて画像にしました。

f:id:ym_AdventC:20191217094541p:plain
和歌_01
さあいざ結果を確認!

 ハ る 自 - 雪
 古 野 の 里 に
 見 る ・ ま て に
 有 明 の 月 ど

なんだ...これは...
元々24文字ある和歌ですが、OCR結果内に和歌に含まれる文字は15文字しか無いですね。
しかも『朝ぼらけ』の行に関しては欠落している。

この結果からAzureのOCRサービスは精度出ませんでしたで終わってはなんとも味気ない...
そこで多少頭を捻り何が悪かったのか考えてみました。

  • 予測①:このOCRサービスは左から読み込み、その結果を最終的に左から横書きで出力しているように見えるので元画像も横書きがよいのでは?
  • 予測②:また選んだ書体には文字の見え方を意識した線や太さの強弱が発生してる、機械に読み込ませるならゴシックのような書体が良いのではないか?

このような予測を立てた上で、一つずつ修正して結果を確認しました。
まず書体を行書体からゴシックに変更してみます。

f:id:ym_AdventC:20191217095429p:plain
和歌_02

降 れ る 白 雪
吉 野 の 里 に
有 明 の 月 と
朝 ぼ ら け
見 る ま で に

文字単位で見れば正しく変換されていますね!
しかし、行単の順番が入れ替わっており、これでは読めない。
では縦横を入れ替えてみるだけだとどうでしょう。

f:id:ym_AdventC:20191217095549p:plain
和歌_03

朝 ば ら け
明 の 月 ど
見 る ま て に
古 野 の 里 に
ハ る 自

『朝ぼらけ』の行は出てきましたが、文字単位では正解率では書体の変更ほど変わらないですね。
最後に書体、縦横両方の変更を加えた結果を確認します。

f:id:ym_AdventC:20191217095704p:plain
和歌_04

朝 ぼ ら け
有 明 の 月 と
見 る ま で に
吉 野 の 里 に
降 れ る 白 雪

!?
読める!
間違いもなく正確に画像データから文字列データに変換されています。
※今回の検証では画像の空白は考慮せず、文字サイズのみ統一しています。

■結果

今回の簡単な検証の結果、文字の変換精度という観点で見ると『書体の変更>縦書き横書きの変更』、行順序の変換精度という観点では『書体の変更<縦書き横書きの変更』でした。
AzureのOCRサービスに画像データを取り込む際にはゴシックのような書体で横書きが良いようです。
(書いてみると当たり前ですが...)

しかし文字精度に書体が影響するのはわかるのですが行順序については、OCRサービス側でどのようなロジックとなっているのかもう少し検証してみたいと思います。
OCRサービス側で前後の行と意味のつながりを意識していたりするのかなど。

■まとめ

今年のMicrosoft Igniteにて機械学習に力を入れていくという発表があり触れてみたいと思っていましたが、このアドベントカレンダーが良いきっかけとなりました。
Azureには引き続き新しいリソース機能がGAされてゆくと思いますので、置いて行かれないようこまめに新しい情報に触れていきたいと思います。

また去年のアドベントカレンダーも12/17に記事を書いたので、
それから色々あったな―と思い返して楽しく記事を書いてました。

次回、Misoca+弥生 Advent Calendar 2019 - Qiita の18日目は、mugi_unoさんです。

C++ネイティブからのC#コード呼び出しの注意点

この記事は Misoca+弥生 Advent Calendar 2019 の16日目エントリーです。

初めまして、弥生のエンジニア、肥後です。

給与チームのエンジニアをしております。

弊社のデスクトップアプリケーションはC++ネイティブとC#のコード双方を利用して開発を行っています。

このうち、C++ネイティブのコードからC#のコードを呼び出す際、ちょっとハマった点についてここで記載させていただきます。

 そもそもやろうとしていたこと

弊社の某アプリケーションでコードクローンが見つかったため、『その解消できそうかとりあえずやってみてくれ』とのお達しがありました。

モジュール構成は以下の通りです。

項目 場所 備考
サードパーティ製品用のプロキシモジュール フォルダA -
上記プロキシ向けのCOMインターフェイス フォルダB 仕様上は弊社製品本体の各モジュールを参照することとなっているが、モジュール参照ではなくコードクローンが一部存在
弊社製品本体(exe、DLL等) フォルダB -

 

とりあえずやってみた結果 

実際開発に苦労してた時期だったからしゃあないなぁ、とか思いながら、クローン元を確認しつつ、該当箇所をDLL呼び出しに変えて動かしみました。

 

クローンの置き換えだけだから問題ないよね~、ポチッとな。

 

・・・

 

なんか落ちたがなorz

 

半泣きになりながらVisual Studioコンソールを確認したところ、EEFileLoadExceptionが出ているとのこと。

 どこで落ちてるのかをステップで確認したところ、以下の『暗号化ユーティリティ』が見つからない模様。

モジュール 場所 言語
プロキシモジュール フォルダA ネイティブ(C++)
COMモジュール フォルダB ネイティブ(C++)
弊社製品本体:データ管理モジュール フォルダB ネイティブ(C++)
弊社製品本体:暗号化ユーティリティ(COMモジュール) フォルダB マネージド(C#)

さて、どうする?

MSDNなどで明確には記載されていないけど、どうやらマネージドのモジュールはそのままでは常に実行モジュールとサイドバイサイドでなくてはならない模様です。

stackoverflow.com

これを回避するためにはリフレクションを使用して動的に処理を呼び出す必要がありますが、ここに記載された内容を見ると、それを使わなくても回避できそうな方法がある様なので試してみました。

  • COMの機構を削除し、C++からの呼び出しにはC++/CLIのモジュール経由に変更
  • 上記C++/CLIモジュール内部で、DLLのパスを指定してあらかじめロードだけしておく
    (AppDomainを生成して)

機能提供元(?)の製品側にも影響が出るので双方で挙動を確認しましたが・・・

呼び出し元 場所 結果
弊社製品 フォルダB OK
プロキシモジュール フォルダA NG

orz

 

さて、どうする?(その2)

マネージドコードでフォルダまたぎでどうにかアクセスさせるとなれば、後はリフレクションを使用する方法が考えられます。ただし、処理を実行する毎にDLLの読み込みが発生する訳でして、当然ながらパフォーマンスの劣化は避けられません。

とりわけ暗号化ユーティリティはテーブル内の文字列項目へのアクセス毎に呼ばれるので、その影響は、大げさな話ではなく計り知れないものとなります。

 ではどうするか・・・、というところで頭を絞った結果、以下の情報をあらかじめキャッシュしておくという方法にたどり着きました

  • モジュールのアセンブリ情報
  • クラス情報
  • メソッド情報やプロパティ情報

 以下、簡単なプログラムで実例を示します。

この例ではプロパティの情報しかAssemblyLoaderUtilで取得していませんが、メソッドの情報を取得する様に使用を拡充することでもう少しやりたいことの幅は広がります。

※動作確認は行っていますが、コードの内容を試す場合は自己責任でお願いします。

また、コメントでも少し触れていますが、C++ネイティブに対して公開するヘッダファイルにはC++/CLI固有の定義やヘッダファイルのインクルードは記載しない様にしてください(コンパイルできなくなります)。

 

○呼び出し対象のC#のコード

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FooBrr
{
    /// <summary>
    /// FizzBuzz判定
    /// </summary>
    public class FizzBuzz
    {
        /// <summary>
        /// Fizz(3の倍数(除く3と5の公倍数))かどうか
        /// </summary>
        public bool IsFizz { get; private set; }

        /// <summary>
        /// Buzz(5の倍数(除く3と5の公倍数))かどうか
        /// </summary>
        public bool IsBuzz { get; private set; }

        /// <summary>
        /// FizzBuzz(除く3と5の公倍数)かどうか
        /// </summary>
        public bool IsFizzBuzz { get;private set; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="num">対象の整数</param>
        public FizzBuzz(UInt32 num)
        {
            this.IsFizzBuzz = (num % 3 == 0 && num % 5 == 0);
            if(this.IsFizzBuzz)
            {
                this.IsFizz = false;
                this.IsBuzz = false;
            }
            else
            {
                this.IsFizz = num % 3 == 0;
                this.IsBuzz = num % 5 == 0;
            }
        }
    }
}

○アセンブリ参照解決用のヘルパ(ヘッダ)

#pragma once

namespace Adapter
{
	/// <summary>
	/// C#モジュールのクラス呼び出し用ユーティリティ
	/// </summary>
	public ref class AssemblyLoaderUtil
	{
	private:
		/// <summary>
		/// モジュール名
		/// </summary>
		System::String^ m_ModuleName;

		/// <summary>
		/// 型情報
		/// </summary>
		System::Type^ m_TargetType;

		/// <summary>
		/// 読み込んでいるアセンブリ情報
		/// </summary>
		System::Reflection::Assembly^ m_Assembly;

		/// <summary>
		/// コンストラクタ情報
		/// </summary>
		System::Reflection::ConstructorInfo^ m_CI;

		/// <summary>
		/// プロパティ情報テーブル
		/// </summary>
		System::Collections::Generic::Dictionary<System::String^, System::Reflection::PropertyInfo^>^ m_PropertyTable;

		/// <summary>
		/// インスタンス情報
		/// </summary>
		System::Object^ instance;

		/// <summary>
		/// モジュール初期化
		/// </summary>
		/// <param name="moduleName">モジュール名</param>
		void InitModule(System::String^ moduleName);

		/// <summary>
		/// インスタンス生成
		/// </summary>
		/// <param name="targetTypeName">対象の型名</param>
		/// <param name="params">コンストラクタ呼び出し用の引数</param>
		void CreateInstance(System::String^ targetTypeName, cli::array<System::Object^>^ params);

		/// <summary>
		/// プロパティ情報の生成
		/// </summary>
		/// <param name="propertyNames">プロパティ名称の一覧</param>
		void CreatePropertyInfo(System::Collections::Generic::List<System::String^>^ propertyNames);

	public:
		/// <summary>
		/// コンストラクタ
		/// </summary>
		/// <param name="moduleName">モジュール名</param>
		/// <param name="targetTypeName">対象の型名称</param>
		/// <param name="params">コンストラクタのパラメータ</param>
		/// <param name="propertyNames">プロパティ名称の一覧</param>
		AssemblyLoaderUtil(System::String^ moduleName, System::String^ targetTypeName, cli::array<System::Object^>^ params, System::Collections::Generic::List<System::String^>^ propertyNames);

		/// <summary>
		/// プロパティのGetter実行
		/// </summary>
		/// <param name="propertyName">プロパティ名</param>
		/// <returns>実行結果</returns>
		System::Object^ InvokePropertyGet(System::String^ propertyName);
	};
}


○アセンブリ参照解決用のヘルパ(実装)

#include "Stdafx.h"
#include "AssemblyLoaderUtil.h"

using namespace Adapter;

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="moduleName">モジュール名</param>
/// <param name="targetTypeName">対象の型名称</param>
/// <param name="params">コンストラクタのパラメータ</param>
/// <param name="propertyNames">プロパティ名称の一覧</param>
AssemblyLoaderUtil::AssemblyLoaderUtil(System::String^ moduleName, System::String^ targetTypeName, cli::array<System::Object^>^ params, System::Collections::Generic::List<System::String^>^ propertyNames)
{
	InitModule(moduleName);

	CreateInstance(targetTypeName, params);
	
	CreatePropertyInfo(propertyNames);
}

/// <summary>
/// モジュール初期化
/// </summary>
/// <param name="moduleName">モジュール名</param>
/// <remarks>
/// <para>ロード対象のDLLとこの処理が所属するDLLが同階層に配置されている場合を想定している。</para>
/// <para>ロード対象のDLLの階層が異なる場合、DLLのパスを何らかの方法で取得してAssembly::LoadFromの引数とすること。</para>
/// </remarks>
void AssemblyLoaderUtil::InitModule(System::String^ moduleName)
{
	// ※実行中コードのパス取得(このDLLのパス)
	System::Reflection::Assembly^ thisAssembly = System::Reflection::Assembly::GetExecutingAssembly();
	System::String^ thisPath = thisAssembly->Location;
	System::String^ directory = System::IO::Path::GetDirectoryName(thisPath);

	this->m_ModuleName = moduleName;
	System::String^ pathToManagedAssembly = System::IO::Path::Combine(directory, this->m_ModuleName);

	// ※モジュールを読み込んでキャッシュ
	this->m_Assembly = System::Reflection::Assembly::LoadFrom(pathToManagedAssembly);
}

/// <summary>
/// インスタンス生成
/// </summary>
/// <param name="targetTypeName">対象の型名</param>
/// <param name="params">コンストラクタ呼び出し用の引数</param>
void AssemblyLoaderUtil::CreateInstance(System::String^ targetTypeName, cli::array<System::Object^>^ params)
{
	this->m_TargetType = this->m_Assembly->GetType(targetTypeName, false);
	if (params == nullptr || params->Length == 0)
	{
		// ※パラメータがない場合はパラメータなしでコンストラクタを取得して実行
		this->m_CI = this->m_TargetType->GetConstructor(System::Type::EmptyTypes);
		cli::array<System::Object^>^ dummy = gcnew cli::array<System::Object^>(0);
		this->instance = this->m_CI->Invoke(dummy);
	}
	else
	{
		// ※パラメータがある場合は該当するパラメータのコンストラクタを取得して実行
		System::Collections::Generic::List<System::Type^>^ typelist = gcnew System::Collections::Generic::List<System::Type^>();
		System::Collections::Generic::List<System::Object^>^ arglist = gcnew System::Collections::Generic::List<System::Object^>();
		for (int i = 0; i < params->Length; i++)
		{
			typelist->Add(params[i]->GetType());
			arglist->Add(params[i]);
		}
		this->m_CI = this->m_TargetType->GetConstructor(typelist->ToArray());
		this->instance = this->m_CI->Invoke(arglist->ToArray());
	}
}

/// <summary>
/// プロパティ情報の生成
/// </summary>
/// <param name="propertyNames">プロパティ名称の一覧</param>
/// <remarks>
/// <para>今回のサンプルは呼び出すプロパティが決まっているので名前の一覧を外部から受け取っている。</para>
/// <para>実際の処理に組み込む場合はpublicのプロパティをすべて取得しておいた方がいいかもしれない。</para>
/// </remarks>
void AssemblyLoaderUtil::CreatePropertyInfo(System::Collections::Generic::List<System::String^>^ propertyNames)
{
	this->m_PropertyTable = gcnew System::Collections::Generic::Dictionary<System::String^, System::Reflection::PropertyInfo^>();
	cli::array<System::Reflection::PropertyInfo^>^ ar = this->m_TargetType->GetProperties();
	for (int i = 0; i < ar->Length; i++)
	{
		this->m_PropertyTable->Add(ar[i]->Name, ar[i]);
	}
}

/// <summary>
/// プロパティのGetter実行
/// </summary>
/// <param name="propertyName">プロパティ名</param>
/// <returns>実行結果</returns>
System::Object^ AssemblyLoaderUtil::InvokePropertyGet(System::String^ propertyName)
{
	// ※指定された名称のプロパティ情報を取得
	System::Reflection::PropertyInfo^ pi = this->m_PropertyTable[propertyName];

	// ※プロパティを実行
	System::Object^ dst = pi->GetValue(this->instance, nullptr);

	return dst;
}

○DLLエクスポート用のヘッダ

#pragma once

#ifdef _EXPORTING
#define CLASS_DECLSPEC    __declspec(dllexport)
#else
#define CLASS_DECLSPEC    __declspec(dllimport)
#endif

○ネイティブに公開されるアダプタ部分(ヘッダ)

// FizzBuzzAdapter.h

#pragma once
#include "DLLExport.h"

namespace Adapter
{
	/// <summary>
	/// C#のFizzBuzz判定呼び出しアダプタ
	/// </summary>
	/// <remarks>
	/// <para>本クラスが所属するDLLはC#のモジュールと同階層となることを想定している。</para>
	/// <para>C#のDLLば別階層となる場合は該当モジュールのパスをこのクラスからAssemblyLoaderUtilに渡す必要あり。</para>
	/// <para>また、このクラスヘッダはネイティブコードに公開されるため、C++/CLIのクラス定義等は記載できないことに注意。</para>
	/// </remarks>
	class CLASS_DECLSPEC FizzBuzzAdapter
	{
	public:
		/// <summary>
		/// Fizz(3の倍数(除く3と5の公倍数))かどうか
		/// </summary>
		/// <returns>条件に該当したらtrueを返す</returns>
		bool IsFizz();

		/// <summary>
		/// Buzz(5の倍数(除く3と5の公倍数))かどうか
		/// </summary>
		/// <returns>条件に該当したらtrueを返す</returns>
		bool IsBuzz();

		/// <summary>
		/// FizzBuzz(除く3と5の公倍数)かどうか
		/// </summary>
		/// <returns>条件に該当したらtrueを返す</returns>
		bool IsFizzBuzz();

		/// <summary>
		/// コンストラクタ
		/// </summary>
		/// <param name="num">対象の整数</param>
		FizzBuzzAdapter(unsigned int num);

	private:
		class Impl;

		/// <summary>
		/// CSharpコード呼び出しをラップするクラスのポインタ
		/// </summary>
		Impl* m_pImpl;
	};
}

○ネイティブに公開されるアダプタ部分(実装)

// これは メイン DLL ファイルです。

#include "stdafx.h"
#include <msclr/gcroot.h>
#include <msclr/marshal_cppstd.h>
#include <string>
#include <string.h>

#include "AssemblyLoaderUtil.h"

#define _EXPORTING
#include "FizzBuzzAdapter.h"

using namespace Adapter;

/// <summary>
/// CLIインスタンス管理クラス
/// </summary>
class FizzBuzzAdapter::Impl
{
public:
	/// <summary>
	/// クラスローダー
	/// </summary>
	msclr::gcroot<AssemblyLoaderUtil^> m_pFizzBuzzClassLoader;

	/// <summary>
	/// コンストラクタ
	/// </summary>
	/// <param name="num">対象の整数</param>
	Impl(unsigned int num)
	{
		cli::array<System::Object^>^ param = { num };
		System::Collections::Generic::List<System::String^>^ props = gcnew System::Collections::Generic::List<System::String^>();
		props->Add("IsFizz");
		props->Add("IsBuzz");
		props->Add("IsFizzBuzz");

		this->m_pFizzBuzzClassLoader = gcnew AssemblyLoaderUtil("FooBrr.dll", "FooBrr.FizzBuzz", param, props);
	}

	/// <summary>
	/// デストラクタ
	/// </summary>
	virtual ~Impl()
	{
	}
};

/// <summary>
/// Fizz(3の倍数(除く3と5の公倍数))かどうか
/// </summary>
/// <returns>条件に該当したらtrueを返す</returns>
bool FizzBuzzAdapter::IsFizz()
{
	return (bool)this->m_pImpl->m_pFizzBuzzClassLoader->InvokePropertyGet("IsFizz");
}

/// <summary>
/// Buzz(5の倍数(除く3と5の公倍数))かどうか
/// </summary>
/// <returns>条件に該当したらtrueを返す</returns>
bool FizzBuzzAdapter::IsBuzz()
{
	return (bool)this->m_pImpl->m_pFizzBuzzClassLoader->InvokePropertyGet("IsBuzz");
}

/// <summary>
/// FizzBuzz(除く3と5の公倍数)かどうか
/// </summary>
/// <returns>条件に該当したらtrueを返す</returns>
bool FizzBuzzAdapter::IsFizzBuzz()
{
	return (bool)this->m_pImpl->m_pFizzBuzzClassLoader->InvokePropertyGet("IsFizzBuzz");
}

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="num">対象の整数</param>
FizzBuzzAdapter::FizzBuzzAdapter(unsigned int num)
{
	this->m_pImpl = new FizzBuzzAdapter::Impl(num);
}

 

 まとめにかえて

.NETに限らず、MS系の技術に関する細かい情報は以外と日本語で出ていないことが多いです。

英語で調べて対応することはもちろんできますが、その時の知見をプロジェクト内に閉じ込めるのももったいない。

同じ箇所で躓く技術者さんは他にも必ずいますので。

そういう意味で、こういうのちゃんと公開することには意義があるのかもと思います。

(中身ちゃんとしてるかと言われればアレですが(^^;)

次回、Misoca+弥生 Advent Calendar 2019 の17日目は、kentaro_tsujinoさんです。

お楽しみに!