C#で扱える環境変数の種類とその取得/設定に関して

案件で環境変数への値の取得/設定に関して少し調査を行ったので、
ブログ用に少し内容増やして調査結果以下に記載します。

C#において扱える環境変数の種類(EnvironmentVariableTarget)は以下の3つです。

Process: プロセスごとの環境変数

User: ユーザごとの環境変数
(システムの詳細設定から確認可能、レジストリ値 HKEY_CURRENT_USER\Environment内)

Machine: PC共通のシステム環境変数
(システムの詳細設定から確認可能。レジストリ値
 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment内)

取得、設定、削除(nullを設定)は以下のメソッドにより可能です。

string? Environment.GetEnvironmentVariable (string variable, EnvironmentVariableTarget target);
void Environment.SetEnvironmentVariable (string variable, string? value, EnvironmentVariableTarget target);

以下の表に、取得/設定時の種類ごとの主な違いをまとめてみました。

以下知見や所感です。
・ユーザ及びマシンは、プロセスの再起動など不要で、
 同時に起動している異なるプロセスにて共有可能でした。

・管理者権限必要なのはマシンだけでした。
 ユーザも内部的にはレジストリ値なので権限必要に思えたのですが、
 HKEY_CURRENT_USER下のキーの変更は権限不要らしいです。

・ユーザとマシンは他のアプリとも共有で影響及ぼすかもなので取得や設定、削除に注意が必要です。
 また、プロセスとは異なり、システム詳細設定やレジストリエディターから
 値が容易に確認可能なため、プロキシ設定などの認証情報の書き込みを行うと
 ユーザIDとパスワードが平文で見えてしまう点も注意です。

・ユーザやマシンへのSetは2秒程度かかり、実行時暫し動作が固まったので、
 場合によってはプログレス表示必要かもしれません。
 内部でレジストリへの書き込みを行う関係か少し時間がかかるようですね。
(参考PCスペック:CPU=intel core i9-13900, RAM=32GB)

・どれを使うかは用途次第ですが、扱いやすいのは管理者権限不要なプロセスとユーザで、
 プロセス間で共有するかどうかで使い分けるのが良いかと思いました。

以上です。

AIチャット機能を実装

今回は弊社で開発しているアプリに実装したAIチャット機能についてご紹介します。
この機能は、アプリをさらにユーザフレンドリーにし、質問応答をスムーズに行えるようにするために導入されました。

AIチャット機能の実現方法

「Azure OpenAI」と「Azure AI Search」を組み合わせて、AIチャット機能を実現しました。以下に、具体的な手順を説明します。

  1. Azure AI Searchで関連する文章を取得
    「Azure AI Search」は、特定のキーワードやトピックに関連する文章を検索できる強力なサービスです。このサービスを活用して、ユーザの質問に適切な回答を提供するための情報を収集しました。
  2. Azure OpenAIで質問の回答を行う
    「Azure OpenAI」は、自然言語処理技術を駆使して、ユーザの質問に対して適切な回答を生成します。「Azure AI Search」で取得した文章をもとに、ユーザの質問に適切な回答を生成するために「Azure OpenAI」を活用しています。

驚くべき効果

実際にこのAIチャット機能を試してみると、以下のような驚くべき効果がありました。

  1. 文章量が少なくても適切な回答が生成される
    「Azure AI Search」で取得する文章量が少なくても、ユーザの質問に対して適切な回答が生成されることに驚きました。これにより、ユーザが短い質問をしても満足のいく回答を得ることができます。
  2. ユーザフレンドリーなアプリに貢献
    AIチャット機能は、ユーザがアプリを使いやすく感じる要素の一つです。この機能によってさらにユーザフレンドリーになり、ユーザの満足度を向上させることができました。

AIチャット機能は、技術アピールにもなりつつ、ユーザフレンドリーなアプリを作成するための非常に有効な手段であると感じています。今後もAI技術を駆使して、さらなるユーザビリティ向上を行っていきます。

AIとプログラム

mtjです。

とある人がAIでプログラマーがなくなると言ってから結構経った気がします。
いずれかはそうなるかもしれませんが 正確性が必要なソフトになればなるほどAIでの自動化は難しいと感じます

絵とか エンタメ等間違っていても問題ないような物ではAIでの自動生成は有用で 速いと思いますが
正確性が必要な部分ではAIで出力したソフトのテスト等 実運用での問題点等を把握できる人の介入が現状どうしても必要です。

これは数年でどうにかなる問題ではないと感じます。
例えばあるOS、環境等では動かないPGが生成された場合それを修正するためには動かないPGを生成させないための仕様等をAIに指示できるエンジニアが必要でしょう
止まっても問題無いようなシステムであれば止まるたびにAIに指示をしエラーを修正させることも可能でしょうが 可用性は皆無だと思います。

限られた環境で動かすプログラムを作るにはそれ相応の知識が必要になると自分は思っていますが
それらを考えずにAIで指示し良いプログラムを作ることができるのかは今後が楽しみではあります。
(特にスレッド関連、メモリ関連、実行速度関連がAIでいい感じに作れると平和になるエンジニアが多いと思います)

AIでプログラマーが無くなるという点についてはなくなると思います。
エンジニアが仕様書をしっかり作るだけでソフト、テストコードまでできるのであればそのエンジニアの下に在籍していた開発者は全員いなくなると思います。

関連を表す矢印には気を付ける

我々は物事の概要を伝える場合に、よく概念図を利用します。
例えばシステム全体図、サーバーとクライアントの関係、クラスAとクラスBの関連などです。
この時関連を表すのに矢印をよく使いますが、図を描くときにも読むときにもとても注意が必要です。

例えばクラス図においては、
A→B はAがBを知っている場合に使いますが、その逆はありません。
同じようにして、
例えばモデルと画面の分離アーキテクチャMVVMを表現すると、V→VM→M となります。
上の2つの例は参照と言い換えることができます。

MVVMを説明する図で V⇆VM⇆M と表現されている場合もあります。
この場合は単にVとMが直接繋がっていないことを強調している場合もあれば、
単純なデータの流れを表しているとも考えられるし、
イベントまで考えると、VはVMを知っているし、VMはイベント発行側なのでVへの参照を持つので、
これもやはり参照を表した矢印と考えることもできます。

大切なのは何を表現しようとした矢印なのかをしっかり考え、
1つの図の中で、参照とデータのような意味の異なる矢印を使わないようにすることです。

Eyeshotにてクリックで3Dオブジェクトの座標を選択

弊社の案件では3Dオブジェクトを表示するのにEyeshotライブラリを使用しています。

ある機能改造にて、Eyeshotで表示している3Dオブジェクトをクリックして座標を選択し、
コードからオブジェクトを基準座標に移動させることとなりました。

クリックして座標選択可能かわからなかったため調べてみたところ、
以下のScreenToWorldというメソッドにより実現できるとわかりました。

public Point3D ScreenToWorld(Point mousePos)

これはEyeshotの3D表示コントロールであるViewportLayoutクラスに定義されたメソッドで、
引数でマウスカーソル位置のスクリーン座標を与えることで、
戻り値として、カーソル下に3D物体があればその表面のワールド座標を、なければnullを返すメソッドとなっています。

これをマウスクリックイベントに登録したメソッド内で呼んで解決……かと思ったのですが、
そうもいかず、座標選択できるものとそうでないものがあり、
どうも3Dオブジェクトが半透明(カラーにα値の設定あり)の場合はScreenToWorldがnullを返してしまうようでした。

ちらつき発生しますが、クリック時に一時的に3Dモデルを不透明(α値最大)とすると座標選択でき、
無事改造内容実現できました。

以上です。

C#でダブルバッファを使って画面のちらつきを防ぐ方法

今回は、C#でダブルバッファを使って画面のちらつきを防ぐ方法についてです。

画面がちらつくという現象は、画面の描画が速く行われるときに起こります。例えばウィンドウをリサイズしたりすると、画面の一部が更新されます。
このとき画面の更新が一瞬で行われると、画面がちらついて見えることがあります。これは、画面の更新がユーザーの目に追いつかないために起こります。

画面のちらつきを防ぐには、ダブルバッファという技術を使います。
ダブルバッファとは、画面に表示するデータを2つのバッファに分けて管理する方法です。
一つのバッファは、画面に表示されるバッファで、もう一つのバッファは、画面に表示する準備をするバッファです。
画面の更新が必要になったときには、準備されたバッファを画面に表示するバッファと入れ替えます。
このようにすると、画面の更新が一瞬で行われるのではなく、スムーズに行われるので、画面のちらつきが防げます。

C#のWindowsFormsでダブルバッファを使うには、DoubleBufferedプロパティを使用します。
DoubleBufferedプロパティは、コントロールのダブルバッファを有効にするかどうかを表すプロパティです。
DoubleBufferedプロパティをtrueにすると、コントロールのダブルバッファが有効になります。
ただし、単に自身のコントロールのDoubleBufferedをtrueにするだけでは、子コントロールのちらつきは防げません。
子コントロールも含めて全てのコントロールのDoubleBufferedをtrueにする必要があります。
しかし、子コントロールのDoubleBufferedは、通常は外部から設定できません。
そこで、リフレクションを使って、外部から子コントロールのDoubleBufferedを設定できるようにし、子コントロールのDoubleBufferedプロパティを再帰的にtrueに設定します。

このようにして、C#でダブルバッファを使って画面のちらつきを防ぐことができます。

変なシステムを作らないために。

こんにちは mtjです。

プログラマーの仕事をしていると 世の中のどうしてこうなった ようなシステムを見るともやもやする機会が増えます。
そこからこうなった原因はどこなんだろうと考えるのも面白いですがそうならないためにを考えていきます。

まず変なシステムを作らないためにはお客さんの要望をしっかり聞く事が大事です
・何をしたいか。
・その後どう使うか。
・どのような人が使うか。
のようにしっかり要点をまとめ、お客さんの依頼通りのシステムだけでなく 用途にあったシステムを作っていくことが重要だと思います。

依頼主は依頼主の知識内でしか想像できませんし、開発側も開発側の知識しかありません。
その2つを上手くすり合わせし必要なシステムを作ることが大事だと思います。

しかし、提案だけではシステムは上手くいきません。
システムはそこから作る人の問題もあるため結局はお客さんの伝える能力、開発側の依頼を受ける能力、開発側の開発自体の能力等すべて揃って初めて良いシステムになると思います。

自分も変なシステムを作らないようにそういった意識を持って開発していきたいと思います。

画面のデザインは難しい

2024年 本年もどうぞよろしくお願い申し上げます。

先日Windowsのソフトで画面の実装を行う機会があり、
それほど複雑な画面ではないものの、ユーザーにとって視認性の高い画面にする必要がありました。

仕様書の段階で画面のイメージは概ね出来上がっていたものの、
実装段階になるとデザインがなかなか上手くできずに困り、
結局、この件ではデザイナの方にデザインをお願いすることになりました。

WPFやMVVMなどのフレームワークが整っている環境でも、
実際にはデザイナではない開発者が、画面のデザインや実装も担当する場合が多いと思います。
その場合でもデザイナの方が行った目標とするデザイン(ゴール)がきちんとあれば、
実装はとてもやりやすいと感じました。

2023年お疲れ様でした

今年も早いものでもう終わりですね。

弊社は今年も本日は午前のみ業務で午後からは大掃除を行い、仕事納めとなります。
年末年始ゆっくりと過ごしたいです。

皆様本年はお世話になりました。
来年も何卒よろしくお願いいたします。
それではよいお年をお迎えください。

Stopwatchクラスの拡張メソッド

C#のStopwatchクラスを使用する際によくあるパターンとして、あるフラグによってスタートまたはリセットしたり、リスタートまたはリセットしたりする場合があります。
せっかくなので拡張メソッドを作成してみました。
実装はしょうもない内容ですが、これによりコードがスッキリします。

public static void StartOrReset(this Stopwatch swbool start) {
    if (start) {
        sw.Start();
    } else {
        sw.Reset();
    }
}
public static void RestartOrReset(this Stopwatch swbool restart) {
    if (restart) {
        sw.Restart();
    } else {
        sw.Reset();
    }
}