10yroの開発日記

福岡にある株式会社10yro(トイロ)のエンジニアが書いています

C# NLogを使ってログ出力する

今回はC#でログ出力する方法です。
以前は自前でクラスを作成することもあったかと思いますが、最近では自作することはほぼないかと思います。
今回はNLogというライブラリを使ってログ出力したいと思います。

nlog-project.org

事前準備1

Visual StudioのNuGetでNLog.Configをインストールします。
NLog.Configをインストールすれば関連するライブラリがインストールされます。

事前準備2

インストールすると、プロジェクト直下に「NLog.config」というログ出力の設定ファイルが作成されます。
しかし、これはリンクで読み取り専用となっており修正できません。
おそらくデフォルトで利用する場合はこのリンクの状態で利用して、もし設定を変更したい場合は上記ファイルをコピーして設定してということだと思います。

設定を変更したいので、コピーして使います。
右クリックメニューから「このアイテムのフォルダーを開く」をクリックします。

エクスプローラーが開くので、NLog.configをコピーします。

Visual Studio上で元々のリンクされたNLog.configを削除した後に、上記でコピーしたNLog.configをプロジェクト配下にコピーします。

出力ディレクトリにコピーは「新しい場合はコピーする」に変更します。

これで修正が可能となります。

設定

NLog.configに設定を追加します。
今回は以下の設定を追加しました。

  • targetsタグ内にファイル出力の設定を追加
  • targetには日本語で出力可能にするために encoding="utf-8" と writeBom="true" を設定
  • rulesタグ内に上記で追加したファイル出力の設定を追加

修正前

修正後

ログ出力

以下の様にログ出力の処理を追加して実行してみます。

bin\Debug配下にlogsディレクトリが生成され、本日日付でログファイルが出力されました。

設定等は色々とあるので、詳細は公式Wikiを参照してください。

github.com

【objective-c】特定のカスタムコントロールを回転時に再描画する方法

こんばんわ。 最近一段と寒いですね。先日も雪が積もってましたので子供に雪遊び誘ったら断られました;;


つい先日、iOSアプリを動作テストしている中でデバイスの回転時(縦と横)にカスタムコントロールのレイアウトが崩れる問題が発生しました。

※カスタムコントロールはdrawRectの中で画面サイズに応じて、frameサイズを決定するような形でが、画面が回転しても再描画されず中途半端なサイズになりました。


って事で画面回転時に再描画を促すような仕組みを作りましたので、備忘録として。

方法としては、回転時のイベントでself.view配下の全てのSubViewをチェックし、特定のクラスであれば再描画を促すって形ですね。

各画面のViewで継承しているベースViewがあれば、ベースに実装したら良いので楽ですね。

// 画面回転時に発生
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
    [coordinator animateAlongsideTransition:^(id context) {
        // 回転前
    } completion:^(id context){
        // 回転後
          [self updateCustomControll:self.view.subviews];
    }];
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

// 再帰的にviewをチェックする。
-(void)updateCustomControl:(NSArray<__kindof UIView *> *) subviews{

    for (UIView* view in subviews) {
        if ([view isMemberOfClass:[CustomUITextField class]] ||
            [view isMemberOfClass:[CustomUIButton class]] ||
            [view isMemberOfClass:[CustomUIWebView class]]) {
          
            // 再描画を促す。
            [view setNeedsDisplay];
        }else{
            if(view.subviews.count >0){
                // 再帰的にsubviewsをチェックする
                [self updateCustomControll:view.subviews];
            }
        }
    }
}

C# Span<T>構造体と配列

C# 7.2以降で追加されたSpan<T>構造体や配列などに追加された演算子についての備忘録です。

Span<T>構造体

Span<T>構造体とは、配列などのデータが並んでいるものから一部を取り出して、値を読み書きするものです。

Span<T>構造体は、ref 構造体という参照型の構造体になっていて、文字列などの連続したメモリ領域を直接参照できるので、バイナリデータなどに対してもC言語ポインターのような感覚で安全に扱うことができます。おまけに高速です。

また、読み取り専用のReadOnlySpan<T>構造体も提供されています。

配列を参照する

// int型の配列
var array = new int[]{ 2022, 1, 23, 4, 0 };

// 2番目から2要素分の参照
var spanData = new Span<int>(array, 1, 2);

foreach (var val in spanData)
{
    Console.Write(val); // 123が出力される
}

spanData[0] = 5; // spanDataの1番目を書き換える

foreach (var val in array)
{
    Console.Write(val); // 202252340が出力される
}

文字列を参照する

以下は、ReadOnlySpan<T>構造体を返す拡張メソッドのAsSpanを使用したサンプルです。
なお、Sliceメソッドは位置と長さを指定してデータを切り出すメソッドです。

// 3文字目から3文字を切り出し
var spanData2 = "XXABCXXX".AsSpan().Slice(2, 3);

foreach (var val in spanData2)
{
    Console.Write(val);   // "ABC" が出力される
}

C#のstring型は変更できないオブジェクトなので、AsSpanメソッドは読み取り専用のReadOnlySpanを返却します。
よって、上記のspanData2は、ReadOnlySpanなので書き換えできません。
なお、配列に対してAsSpanメソッドを使う場合は、Spanを返します。

インデックスと範囲の演算子「^」「..」

C# 8.0で追加された演算子^..を使うと、範囲を指定する構文がシンプルに記述できるようになりました。

Span<T>ReadOnlySpan<T>に範囲を指定する際にも使用できます。

例)
 ^N: 末尾からN番目

 N..^M: N+1からM番目。※先頭の位置は0から数える。

var array = new int[]{ 2022, 1, 23, 4, 0 };

// 末尾から3番目
Console.WriteLine(array [^3]); // 23が出力される

// 2~4番目
var span = array.AsSpan()[1..4];

foreach (var val in span)
{
    Console.Write(val); // 1234が出力される
}

【Excel】新関数の紹介

Excel 2021で追加になった関数や機能をご紹介します。
2021以前の関数についても記載しますが結構な数があります。
気になる関数や活用できそうな関数は、今のうちにチェックして、実際に使用してみてください。今回は実際の使用方法は記載しないので紹介のみとなります。
Microsoft 365のみでしか使用できませんが、LAMBDAを使用すれば、新たな関数を独自に定義できる(作成できる)ようになりました。

種類 関数 説明 Excel 2021 Microsoft 365
文字列 ASC/JIS 全角文字または半角文字に変換する
文字列 CHAR/UNICHAR 文字コードに対応する文字を返す
文字列 CLEAN 印刷できない文字を削除する
文字列 CODE/UNICODE 文字コードを調べる
文字列 CONCATENATE 文字列を連結する
文字列 EXACT 文字列が等しいかどうかを調べる
文字列 FIND/FINDB 文字列の位置またはバイト位置を調べる
文字列 LAMBDA 変数と数式を指定し、関数として利用する
文字列 LEFT/LEFTB 左端から何文字かまたは何バイトかを取り出す
文字列 LEN/LENB 文字列の文字数またはバイト数を求める
文字列 LET 名前を付けた計算結果や値を関数内で利用する
文字列 MID/MIDB 指定した位置から何文字かまたは何バイトかを取り出す
文字列 NUMBERSTRING 数値を漢数字の文字列に変換する
文字列 PHONETIC ふりがなを取り出す
文字列 PROPER 英単語の先頭文字だけを大文字に変換する
文字列 REPLACE/REPLACEB 指定した文字数またはバイト数の文字列を置き換える
文字列 REPT 指定した回数だけ文字列を繰り返す
文字列 RIGHT/RIGHTB 右端から何文字かまたは何バイトかを取り出す
文字列 SEARCH/SEARCHB 文字列の位置またはバイト位置を調べる
文字列 SUBSTITUTE 検索した文字列を置き換える
文字列 TEXT 数値に表示形式を適用した文字列を返す
文字列 TEXTJOIN 区切り記号を挿入しながら複数の文字列を連結する
文字列 TRIM 余計な空白文字を削除する
文字列 UPPER/LOWER 英字を大文字または小文字に変換する
数値 ABS 絶対値を求める
数値 AGGREGATE さまざまな集計値を求める
数値 AVERAGE/AVERAGEA 数値またはデータの平均値を求める
数値 AVERAGEIF 条件を指定して数値の平均を求める
数値 AVERAGEIFS 複数の条件を指定して数値の平均を求める
数値 CEILING 数値を基準値の倍数に切り上げる
数値 COS 余弦を求める
数値 COUNT/COUNTA 数値や日付、時刻またはデータの個数を求める
数値 COUNTBLANK 空白セルの個数を求める
数値 COUNTIF 条件に一致するデータの個数を求める
数値 COUNTIFS 複数の条件に一致するデータの個数を求める
数値 DAVERAGE 条件を満たすセルの平均を求める
数値 DCOUNT 条件を満たす数値の個数を求める
数値 DCOUNTA 条件を満たす空白以外のセルの個数を求める
数値 DEGREES ラジアンを度に変換する
数値 DSUM 条件を満たすセルの合計を求める
数値 EVEN/ODD 最も近い偶数または奇数になるように切り上げる
数値 FIXED 数値に桁区切り記号と小数点を付ける
数値 FLOOR 数値を基準値の倍数に切り下げる
数値 FREQUENCY 区間に含まれる値の個数を求める
数値 INT 小数点以下を切り捨てる
数値 LARGE 大きいほうから何番目かの値を求める
数値 MAX/MAXA 数値またはデータの最大値を求める
数値 MEDIAN 数値の中央値を求める
数値 MIN/MINA 数値またはデータの最小値を求める
数値 MOD 余りを求める
数値 MODE/MODE.SNGL 数値の最頻値を求める
数値 MODE.MULT 複数の最頻値を求める
数値 MROUND 指定した数値の倍数になるように丸める
数値 PRODUCT 積を求める
数値 QUOTIENT 整数商を求める
数値 RADIANS 度をラジアンに変換する
数値 RAND 乱数を発生させる(0以上1未満の整数)
数値 RANDARRAY 乱数が入った配列を作成する
数値 RANDBETWEEN 乱数を発生させる(整数)
数値 RANK/RANK.EQ 順位を求める(同じ値のときは最上位の順位を返す)
数値 RANK.AVG 順位を求める(同じ値のときは平均値の順位を返す)
数値 ROUND 指定した桁数で四捨五入する
数値 ROUNDDOWN/TRUNC 指定した桁数で切り捨てる
数値 ROUNDUP 指定した桁数で切り上げる
数値 SEQUENCE 等差数列が入った配列を作成する
数値 SIN 正弦を求める
数値 SMALL 小さいほうから何番目かの値を求める
数値 SUBTOTAL さまざまな集計値を求める
数値 SUM 数値を合計する
数値 SUMIF 条件を指定して数値を合計する
数値 SUMIFS 複数の条件を指定して数値を合計する
数値 SUMPRODUCT 配列要素の積を合計する
数値 SUMPRODUCT 配列要素の積を合計する
数値 TAN 正接を求める
検索 ADDRESS 行番号と列番号からセル参照の文字列を求める
検索 ADDRESS 行番号と列番号からセル参照の文字列を求める
検索 CHOOSE 引数のリストから値を選ぶ
検索 COLUMN セルの列番号を求める
検索 COLUMNS 列数を求める
検索 FILTER 条件に一致する行を抽出する
検索 FIELDVALUE 株価データや地理データの値を取り出す
検索 HLOOKUP 範囲を横方向に検索する
検索 HYPERLINK ハイパーリンクを作成する
検索 INDEX 行と列で指定した位置の値を求める
検索 INDIRECT 参照文字列をもとにセルを間接参照する
検索 INDIRECT 参照文字列をもとにセルを間接参照する
検索 LOOKUP 1行または1列の範囲を検索する
検索 MATCH 検索値の相対位置を求める
検索 OFFSET 行と列で指定したセルのセル参照を求める
検索 ROW セルの行番号を求める
検索 ROWS 行数を求める
検索 SORT データを並べて取り出す
検索 SORTBY データを複数の基準で並べて取り出す
検索 TRANSPOSE 行と列の位置を入れ替える
検索 TRANSPOSE 行と列の位置を入れ替える
検索 UNIQUE 重複するデータをまとめる
検索 VLOOKUP 範囲を縦方向に検索する
検索 XMATCH 検索値の相対位置を求める
検索 XLOOKUP 範囲を下に向かって検索し対応する値を返す
日付/時刻 DATE 年、月、日から日付を求める
日付/時刻 DATEDIF 期間内の年数、月数、日数を求める
日付/時刻 DATESTRING 日付を和暦に変換する
日付/時刻 DATEVALUE 日付を表す文字列からシリアル値を求める
日付/時刻 DAY 日付から「日」を取り出す
日付/時刻 EDATE 数カ月前や数カ月後の日付を求める
日付/時刻 EOMONTH 数カ月前や数カ月後の月末を求める
日付/時刻 HOUR 時刻から「時」を取り出す
日付/時刻 ISOWEEKNUM ISO8601方式で日付が何週目かを求める
日付/時刻 MINUTE 時刻から「分」を取り出す
日付/時刻 MONTH 日付から「月」を取り出す
日付/時刻 NETWORKDAYS 土日と祭日を除外して期間内の日数を求める
日付/時刻 NETWORKDAYS.INTL 指定した休日を除外して期間内の日数を求める
日付/時刻 NOW/TODAY 現在の日付、または現在の日付と時刻を求める
日付/時刻 SECOND 時刻から「秒」を取り出す
日付/時刻 TEXT 数値に表示形式を適用した文字列を返す
日付/時刻 TEXT 数値に表示形式を適用した文字列を返す
日付/時刻 TIME 時、分、秒から時刻を求める
日付/時刻 TIMEVALUE 時刻を表す文字列からシリアル値を求める
日付/時刻 WEEKDAY 日付から曜日を取り出す
日付/時刻 WEEKDAY 日付から曜日を取り出す
日付/時刻 WEEKNUM 日付が何週目かを求める
日付/時刻 WEEKNUM 日付が何週目かを求める
日付/時刻 WORKDAY 土日と祭日を除外して期日を求める
日付/時刻 WORKDAY.INTL 指定した休日を除外して期日を求める
日付/時刻 YEAR 日付から「年」を取り出す
セル CELL セルの情報を得る
セル FORMULATEXT 数式を取り出す
セル INFO 現在の操作環境についての情報を得る
セル ISBLANK 空白セルかどうかを調べる
セル ISEVEN/ISODD 偶数か奇数かどうかを調べる
セル ISFORMULA 数式かどうかを調べる
セル ISLOGICAL 論理値かどうかを調べる
セル ISTEXT/ISNONTEXT 文字列か文字列以外かどうかを調べる
セル ISNUMBER 数値かどうかを調べる
セル TYPE データの種類を調べる
エラー処理 IF 条件によって利用する式を変える
エラー処理 IFERROR/IFNA エラーの場合に返す値を指定する
エラー処理 ISERROR/ISERR エラー値かどうかを調べる

support.microsoft.com support.microsoft.com

C# 8.0 のswitch式について

switch式

C# 8.0から、switch式という機能が追加されました。

従来のswitch構文は、C言語の構文を踏襲したもので、ちょっと使いにくかったのですが、シンプルな式として書けるようになりました。

構文としては、以下のようになります。

変数 switch
{
    パターン1 => 式1, ・・・パターンn => 式n,  
}

例えば、、、

現在の月によって、以下の文字をコンソールに出力するコードを用意します。

  • 1~4月の場合、月を英語(略語)で出力
  • 7~8月の場合、「summer vacation!!!」を出力
  • 上記以外の月の場合、数字で「〇月」を出力

従来のswitch構文の場合

    int month = DateTime.Now.Month;
    string str;

    switch (month)
    {
        case 1:
            str = "Jan.";
            break;
        case 2:
            str = "Feb.";
            break;
        case 3:
            str = "Mar.";
            break;
        case 4:
            str = "Apr.";
            break;
        case 7:
        case 8:
            str = "summer vacation!!!";
            break;
        default:
            str = month + "月";
            break;
    }

    Console.WriteLine(str);

これをC# 8.0のswitch式で書き換えると、以下のようになります。

    int month = DateTime.Now.Month;
    var str = month switch
    {
        1 => "Jan.",
        2 => "Feb.",
        3 => "Mar.",
        4 => "Apr.",
        _ when month == 7 || month == 8 => "summer vacation!!!",
        _ => month + "月"
    };

    Console.WriteLine(str);

caseやbreakなどは不要となり、ダラダラと書かなくて済みます。

また、defaultの処理は、_(アンダースコア)とマッチさせます。

以上、switch式の備忘録でした。

C#でClosedXMLを使ってExcelを操作する

今回はC#Microsoft OfficeExcelを利用する場合のサンプルです。

やり方はいくつかあるかと思いますが、今回はClosedXMLというパッケージを利用します。

ClosedXMLのインストール

NuGetからClosedXMLをインストールします。
簡単ですね。

ExcelのBookを新規作成する

以下はClosedXMLを使ってExcelのBookを新規作成、Sheetを追加し、セル(B2)に値をセットしています。
また、B2に罫線も引きました。

上記を実行すると、以下のExcelが保存されました。

ExcelのBookのSheetを読み取る

今度は上記で作成したBookからCell B2の値を読み取ってコンソールに表示します。

上記を実行すると下記のようにCell B2の値「Closed XML Sample」が出力されました。

まとめ

C#Excelを操作したい場合はClosedXMLを使うのがオススメです。
ClosedXMLの使い方は以下の公式のWikiを参考にすると良いと思います。

github.com

【Angular】ブラウザでカメラを利用する

Angular アプリケーションでデバイスのカメラを利用する方法を記載します。

Angular でのカメラ利用は Navigator.mediaDevices を利用すれば実現できます。

各ブラウザの対応状況については以下を参照下さい。 https://developer.mozilla.org/ja/docs/Web/API/Navigator/mediaDevices

1. <video>を用意する。

まずはHTML側に<video> を配置します。

そして、それぞれのElementをViewChildとして定義しておきます。

HTML

<video id="camera" #camera autoplay></video>

TS

@ViewChild('camera') camera?: ElementRef;
2. カメラを起動する処理を追加

次にカメラ起動に関する処理を記載します。

カメラを起動するには MediaDevices.getUserMedia() を利用します。

getUserMedia() の引数として MediaStreamConstraintsを定義しておきます。(引数に直接指定しても問題ないです。)

constraints: MediaStreamConstraints = {
    audio: false,
    video: {
        //  environment : Rear camera / user : Self-view camera
        facingMode: 'environment' as VideoFacingModeEnum,
    },
};

<video>のsrcObjectに対し取得したMediaStreamをセットします。

カメラを起動する処理は AfterViewInit 等のタイミングで呼び出せば良いと思います。

if (!navigator.mediaDevices) {
    // 【navigator.mediaDevicesが取得できない場合の処理】
    return;
}
navigator.mediaDevices
    .getUserMedia(this.constraints)
    .then((stream: MediaStream) => {
        if (this.camera) {
            this.camera.nativeElement.srcObject = stream;
        }
    })
    .catch((error) => {
        // 【エラーが発生した場合の処理】
    });
3. カメラを停止する処理を追加

OnDestroy 等カメラを停止したいタイミングで MediaStreamTrack.stop()をコールします。

if (this.camera?.nativeElement.srcObject) {
    const track = this.camera.nativeElement.srcObject.getTracks()[0] as MediaStreamTrack;
    track.stop();
}
4. 撮影処理を追加

これで基本的にカメラの起動/停止ができると思うので

後は撮影処理を実装すればカメラ機能として利用できます。

以下は撮影した画像をData URLとして取得する例です。(<canvas>を利用しています。)

HTML

<canvas id="canvas" #canvas></canvas>

TS

let width = this.camera?.nativeElement.clientWidth;
let height = this.camera?.nativeElement.clientHeight;

if (this.canvas) {
    const context = this.canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D;
    this.canvas.nativeElement.width = width;
    this.canvas.nativeElement.height = height;
    const dataUrl = this.canvas.nativeElement.toDataURL(
        context.drawImage(this.camera?.nativeElement, 0, 0, width, height)
    );

    this.dialogRef.close(dataUrl);
}
注意点

ブラウザのカメラはセキュリティ上、基本的にHTTPSの場合かローカル(localhost)の場合のみ利用できるようになっており、 HTTPでは利用できません。(navigator.mediaDevices が取得できません。)

サンプル

以下サンプルとなります。

※ここではカメラをダイアログ表示(全画面)するものしMatDialogRefを入れています。

HTML

<video id="camera" #camera autoplay></video>
<canvas id="canvas" #canvas></canvas>

※<button>や<img>等 撮影ボタンや必要に応じて閉じるボタン等を配置

TS

export class CameraComponent implements AfterViewInit, OnDestroy {
    @ViewChild('camera') camera?: ElementRef;
    @ViewChild('canvas') canvas?: ElementRef;
    
    constraints: MediaStreamConstraints = {
        audio: false,
        video: {
            //  environment : Rear camera / user : Self-view camera
            facingMode: 'environment' as VideoFacingModeEnum,
        },
    };
    
    constructor(private dialog: MatDialog, private dialogRef: MatDialogRef<CameraComponent>) {}
    
    ngAfterViewInit(): void {
        this.startCamera();
    }

    ngOnDestroy(): void {
        this.stopCamera();
    }

    closeDialog() {
        this.dialogRef.close();
    }
    
    startCamera() {
        if (!navigator.mediaDevices) {
            alert('camera is not supported.');
            return;
        }
        navigator.mediaDevices
            .getUserMedia(this.constraints)
            .then((stream: MediaStream) => {
                if (this.camera) {
                    this.camera.nativeElement.srcObject = stream;
                }
            })
            .catch((error) => {
                // 【エラーが発生した場合の処理】
            });
    }
    
    stopCamera() {
        if (this.camera?.nativeElement.srcObject) {
            const track = this.camera.nativeElement.srcObject.getTracks()[0] as MediaStreamTrack;
            track.stop();
        }
    }
    
    shoot() {
        let width = this.camera?.nativeElement.clientWidth;
        let height = this.camera?.nativeElement.clientHeight;

        if (this.canvas) {
            const context = this.canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D;
            this.canvas.nativeElement.width = width;
            this.canvas.nativeElement.height = height;
            const dataUrl = this.canvas.nativeElement.toDataURL(
                context.drawImage(this.camera?.nativeElement, 0, 0, width, height)
            );

            this.dialogRef.close(dataUrl);
        }
    }
}