20210325のC#に関する記事は11件です。

TShockサーバーでGhostの移動を快適にする

前提

注意

  • 回り道します。悪しからず。

プロジェクト準備

  1. プロジェクト新規作成
  2. Class Library (.NET Framework)で作成
  3. DLしたTShockから、TerrariaServer.exeOTAPI.dllServerPlugins/TShockAPI.dllNewtonsoft.Json.dll(オプション)を参照に追加

TShockプラグインとしての最小限の形

TerrariaPlugin継承して警告を適当に直していればこんな形になります

[ApiVersion(2, 1)]
public class GhostMain : TerrariaPlugin
{
    public override string Author => "YOUの名前";

    public override string Description => "プラグイン説明";

    public override string Name => "TeleportGhost";

    public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;

    public GhostMain(Main game)
        : base(game)
    {
    }

    public override void Initialize()
    {
        // ここでHook登録
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // ここでHook削除
        }

        base.Dispose(disposing);
    }
}

ビルド後はプラグイン名.dllをServerPluginsディレクトリに入れる

ゴースト移動を加速させよう

サーバー通信量増やせばできるけど、まあできません

じゃあどうするの?

短距離テレポートさせればいいじゃない
※移動加速も1秒に60回通常移動以上の距離テレポートさせれば可能です。通信量の無駄

短距離テレポート、やってみよう!

まずはゴーストが移動したことを知る

基本的にTerrariaのプレイヤーの移動はPlayerUpdateパケットで送られます。ゴーストも例に漏れません。
なので、PlayerUpdateフックを探してそこで処理します。
HookはOTAPI.HooksServerApi.HooksTShockAPI.Hooksのどれかに大体あります。
...ありませんでした!

仕方がないので自分で通信処理

通信を受け取るところにHookして処理を書きます

public override void Initialize()
{
    ServerApi.Hooks.NetGetData.Register(this, OnGetData);
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        ServerApi.Hooks.NetGetData.Deregister(this, OnGetData);
    }
}

private void OnGetData(GetDataEventArgs args)
{
    if (args.Handled)
    {
        return;
    }

    switch (args.MsgID)
    {
        case PacketTypes.PlayerUpdate:
            OnPlayerUpdate(args);
            break;
    }
}

private void OnPlayerUpdate(GetDataEventArgs args)
{
    // ゴーストの時だけ処理
    if (!Main.player[args.Msg.whoAmI].ghost)
    {
        return;
    }

    // 処理するので処理しましたフラグを建てる
    args.Handled = true;

    // 今回はゴーストが移動したときにテレポートさせたいだけなので、一度テラリアサーバーが行う処理と同じことをする。
    BinaryReader reader = new BinaryReader(new MemoryStream(args.Msg.readBuffer, args.Index, args.Length));
    int playerIndex = reader.ReadByte();
    if (playerIndex != Main.myPlayer || Main.ServerSideCharacter)
    {
        Player player = Main.player[playerIndex];

        BitsByte bitsByte = reader.ReadByte();
        BitsByte bitsByte1 = reader.ReadByte();
        BitsByte bitsByte2 = reader.ReadByte();
        BitsByte bitsByte3 = reader.ReadByte();
        player.controlUp = bitsByte[0];
        player.controlDown = bitsByte[1];
        player.controlLeft = bitsByte[2];
        player.controlRight = bitsByte[3];
        player.controlJump = bitsByte[4];
        player.controlUseItem = bitsByte[5];
        player.direction = (bitsByte[6] ? 1 : (-1));
        if (bitsByte1[0])
        {
            player.pulley = true;
            player.pulleyDir = (byte)((!bitsByte1[1]) ? 1 : 2);
        }
        else
        {
            player.pulley = false;
        }

        player.vortexStealthActive = bitsByte1[3];
        player.gravDir = (bitsByte1[4] ? 1 : (-1));
        player.TryTogglingShield(bitsByte1[5]);
        player.ghost = bitsByte1[6];
        player.selectedItem = reader.ReadByte();
        player.position = reader.ReadVector2();
        if (bitsByte1[2])
        {
            player.velocity = reader.ReadVector2();
        }
        else
        {
            player.velocity = Vector2.Zero;
        }

        if (bitsByte2[6])
        {
            player.PotionOfReturnOriginalUsePosition = reader.ReadVector2();
            player.PotionOfReturnHomePosition = reader.ReadVector2();
        }
        else
        {
            player.PotionOfReturnOriginalUsePosition = null;
            player.PotionOfReturnHomePosition = null;
        }

        player.tryKeepingHoveringUp = bitsByte2[0];
        player.IsVoidVaultEnabled = bitsByte2[1];
        player.sitting.isSitting = bitsByte2[2];
        player.downedDD2EventAnyDifficulty = bitsByte2[3];
        player.isPettingAnimal = bitsByte2[4];
        player.isTheAnimalBeingPetSmall = bitsByte2[5];
        player.tryKeepingHoveringDown = bitsByte2[7];
        player.sleeping.SetIsSleepingAndAdjustPlayerRotation(player, bitsByte3[0]);

        // ゴーストを短距離テレポート
        ShortTeleportGhost(player);

        // ゴーストにタイル情報送信
        RemoteClient.CheckSection(playerIndex, player.position);
    }
}

private void ShortTeleportGhost(Player player)
{
    // 今は何もしない
}

これでゴーストが移動しようとしたときに何かしらの処理ができるようになりました。

テレポートの仕様を考える

  • プレイヤーが意図したときにテレポートさせるようにしたい
  • こちらが使える情報はゴーストの現在位置と入力の切り替わり(上下左右)

この2つの条件から、ダブルタップ(同じキーを2度押し)でテレポートさせるのが良さそうと考えました。

ダブルタップ検知

実装する前に、「ダブルタップ」がどういう状態なのかを考えます。
image.png

1個の入力に対し、「入力あり」と「入力なし」の2つの状態があります。
「入力あり」から「入力なし」への切り替わりでタイマーをスタートさせ、「入力なし」から「入力あり」への切り替わりでタイマーをストップ、そのタイマーの時間でダブルタップを判断することにしました。

実際の実装では、処理速度低減のため、常に1個だけタイマーを動かし、そのタイマーの時間を切り替わりで個別に記録することで判定させています。

// 配列に入力切り替わり時間と前回の入力を保存しておく(上左下右)
private int[,] lastControlChangedTicks = new int[Main.player.Length, 4];

private bool[,] lastPlayerControl = new bool[Main.player.Length, 4];

private int currentGameTick = 0;

private const int SHORT_TELEPORT_DOUBLE_TAP_TICK = 20;

private const int SHORT_TELEPORT_DISTANCE_X = 320;

private const int SHORT_TELEPORT_DISTANCE_Y = 320;

public override void Initialize()
{
    ServerApi.Hooks.NetGetData.Register(this, OnGetData);
    ServerApi.Hooks.GameUpdate.Register(this, UpdateGameTick);
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        ServerApi.Hooks.NetGetData.Deregister(this, OnGetData);
        ServerApi.Hooks.GameUpdate.Deregister(this, UpdateGameTick);
    }
}

private void UpdateGameTick(EventArgs args)
{
    currentGameTick++;
}

private void ShortTeleportGhost(Player player)
{
    int playerIndex = player.whoAmI;

    // テレポート相対位置
    Vector2 teleportOffset = Vector2.Zero;
    if (player.controlUp || player.controlJump)
    {
        if (!lastPlayerControl[playerIndex, 0])
        {
            // プレイヤーコントロール入力変化(上入力なし→あり)
            if (lastControlChangedTicks[playerIndex, 0] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
            {
                teleportOffset.Y -= SHORT_TELEPORT_DISTANCE_Y;
            }
        }
    }
    else
    {
        if (lastPlayerControl[playerIndex, 0])
        {
            // プレイヤーコントロール入力変化(上入力あり→なし)
            lastControlChangedTicks[playerIndex, 0] = currentGameTick;
        }
    }

    if (player.controlLeft)
    {
        if (!lastPlayerControl[playerIndex, 1])
        {
            // プレイヤーコントロール入力変化(左入力なし→あり)
            if (lastControlChangedTicks[playerIndex, 1] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
            {
                teleportOffset.X -= SHORT_TELEPORT_DISTANCE_X;
            }
        }
    }
    else
    {
        if (lastPlayerControl[playerIndex, 1])
        {
            // プレイヤーコントロール入力変化(上入力あり→なし)
            lastControlChangedTicks[playerIndex, 1] = currentGameTick;
        }
    }

    if (player.controlDown)
    {
        if (!lastPlayerControl[playerIndex, 2])
        {
            // プレイヤーコントロール入力変化(下入力なし→あり)
            if (lastControlChangedTicks[playerIndex, 2] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
            {
                teleportOffset.Y += SHORT_TELEPORT_DISTANCE_Y;
            }
        }
    }
    else
    {
        if (lastPlayerControl[playerIndex, 2])
        {
            // プレイヤーコントロール入力変化(左入力あり→なし)
            lastControlChangedTicks[playerIndex, 2] = currentGameTick;
        }
    }

    if (player.controlRight)
    {
        if (!lastPlayerControl[playerIndex, 3])
        {
            // プレイヤーコントロール入力変化(右入力なし→あり)
            if (lastControlChangedTicks[playerIndex, 3] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
            {
                teleportOffset.X += SHORT_TELEPORT_DISTANCE_X;
            }
        }
    }
    else
    {
        if (lastPlayerControl[playerIndex, 3])
        {
            // プレイヤーコントロール入力変化(右入力あり→なし)
            lastControlChangedTicks[playerIndex, 3] = currentGameTick;
        }
    }

    // プレイヤー入力を記録(パケットが来るまで入力は変化しないため、リセット等は必要ない)
    lastPlayerControl[playerIndex, 0] = player.controlUp || player.controlJump;
    lastPlayerControl[playerIndex, 1] = player.controlLeft;
    lastPlayerControl[playerIndex, 2] = player.controlDown;
    lastPlayerControl[playerIndex, 3] = player.controlRight;

    // TODO: 実際にテレポートさせる
}

実際にテレポートさせるぞ!

ゴーストの移動も検知でき、ダブルタップ検知もでき、あとはゴーストをテレポートさせるだけで完成です。楽勝ですね。
しかも、テレポート関数が最初から用意されています!最高です!

player.Teleport(player.position + teleportOffset);

...とはいかないんですね。テラリアなので。
この関数で、ちゃんとテレポートします(ディスコ杖のテレポートと同じです)。何が問題なのでしょうか?

それは、他のプレイヤーからテレポートエフェクトが見える上、ゴーストになったプレイヤーが見えてしまいます

それくらい妥協しない?

妥協しません。
player.Teleport()ゴミ使えないということがわかったので、※今回のケース
別の方法を使います。

テラリアはサーバーから送られてきたプレイヤーデータを正とすることは少なく、例えばテレポートなどがクライアントで処理され、反映されます。他のもの(プレイヤー移動、武器使用など)はクライアントに無視されます。しかし、例外があります。

キャラクターがサーバーで管理されている場合です

影響のないテレポート

長かったですが、これで終わりです。
キャラクターを一時的にサーバーサイドキャラクターとして扱い、その後戻すことによってテレポートを実現させています。

private void ShortTeleportGhost(Player player)
{
    // (省略)
    // 移動距離が0以外の場合
    if (teleportOffset != Vector2.Zero)
    {
        bool isSSC = Main.ServerSideCharacter;
        TSPlayer tsPlayer = TShock.Players[playerIndex];

        // サーバーサイドキャラクターではない場合、サーバーサイドキャラクターに一時的にする
        if (!isSSC)
        {
            Main.ServerSideCharacter = true;
            NetMessage.SendData((int)PacketTypes.WorldInfo, playerIndex, -1, null, 0, 0f, 0f, 0f, 0, 0, 0);
            tsPlayer.IgnoreSSCPackets = true;
        }

        player.position += teleportOffset;

        // プレイヤーがワールド端に行き過ぎないように補正
        if (player.position.X > Main.rightWorld - 992)
        {
            player.position.X = Main.rightWorld - 992;
        }
        if (player.position.X < 992)
        {
            player.position.X = 992;
        }
        if (player.position.Y > Main.bottomWorld - 992)
        {
            player.position.Y = Main.bottomWorld - 992;
        }
        if (player.position.Y < 992)
        {
            player.position.Y = 992;
        }

        // クライアントにプレイヤー情報送信(事実上のテレポート)
        NetMessage.SendData((int)PacketTypes.PlayerUpdate, playerIndex, -1, null, playerIndex);

        // サーバーサイドキャラクターではなかった場合、元に戻す
        if (!isSSC)
        {
            Main.ServerSideCharacter = false;
            NetMessage.SendData((int)PacketTypes.WorldInfo, playerIndex, -1, null, 0, 0f, 0f, 0f, 0, 0, 0);
            tsPlayer.IgnoreSSCPackets = false;
        }
    }
}

コード全体

[ApiVersion(2, 1)]
public class GhostMain : TerrariaPlugin
{
    public override string Author => "YOUの名前";

    public override string Description => "プラグイン説明";

    public override string Name => "TeleportGhost";

    public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;

    // 配列に入力切り替わり時間と前回の入力を保存しておく(上左下右)
    private int[,] lastControlChangedTicks = new int[Main.player.Length, 4];

    private bool[,] lastPlayerControl = new bool[Main.player.Length, 4];

    private int currentGameTick = 0;

    private const int SHORT_TELEPORT_DOUBLE_TAP_TICK = 20;

    private const int SHORT_TELEPORT_DISTANCE_X = 320;

    private const int SHORT_TELEPORT_DISTANCE_Y = 320;

    public GhostMain(Main game)
        : base(game)
    {
    }

    public override void Initialize()
    {
        ServerApi.Hooks.NetGetData.Register(this, OnGetData);
        ServerApi.Hooks.GameUpdate.Register(this, UpdateGameTick);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            ServerApi.Hooks.NetGetData.Deregister(this, OnGetData);
            ServerApi.Hooks.GameUpdate.Deregister(this, UpdateGameTick);
        }
    }

    private void UpdateGameTick(EventArgs args)
    {
        currentGameTick++;
    }

    private void OnGetData(GetDataEventArgs args)
    {
        if (args.Handled)
        {
            return;
        }

        switch (args.MsgID)
        {
            case PacketTypes.PlayerUpdate:
                OnPlayerUpdate(args);
                break;
        }
    }

    private void OnPlayerUpdate(GetDataEventArgs args)
    {
        // ゴーストの時だけ処理
        if (!Main.player[args.Msg.whoAmI].ghost)
        {
            return;
        }

        // 処理するので処理しましたフラグを建てる
        args.Handled = true;

        // 今回はゴーストが移動したときにテレポートさせたいだけなので、一度テラリアサーバーが行う処理と同じことをする。
        BinaryReader reader = new BinaryReader(new MemoryStream(args.Msg.readBuffer, args.Index, args.Length));
        int playerIndex = reader.ReadByte();
        if (playerIndex != Main.myPlayer || Main.ServerSideCharacter)
        {
            Player player = Main.player[playerIndex];

            BitsByte bitsByte = reader.ReadByte();
            BitsByte bitsByte1 = reader.ReadByte();
            BitsByte bitsByte2 = reader.ReadByte();
            BitsByte bitsByte3 = reader.ReadByte();
            player.controlUp = bitsByte[0];
            player.controlDown = bitsByte[1];
            player.controlLeft = bitsByte[2];
            player.controlRight = bitsByte[3];
            player.controlJump = bitsByte[4];
            player.controlUseItem = bitsByte[5];
            player.direction = (bitsByte[6] ? 1 : (-1));
            if (bitsByte1[0])
            {
                player.pulley = true;
                player.pulleyDir = (byte)((!bitsByte1[1]) ? 1 : 2);
            }
            else
            {
                player.pulley = false;
            }

            player.vortexStealthActive = bitsByte1[3];
            player.gravDir = (bitsByte1[4] ? 1 : (-1));
            player.TryTogglingShield(bitsByte1[5]);
            player.ghost = bitsByte1[6];
            player.selectedItem = reader.ReadByte();
            player.position = reader.ReadVector2();
            if (bitsByte1[2])
            {
                player.velocity = reader.ReadVector2();
            }
            else
            {
                player.velocity = Vector2.Zero;
            }

            if (bitsByte2[6])
            {
                player.PotionOfReturnOriginalUsePosition = reader.ReadVector2();
                player.PotionOfReturnHomePosition = reader.ReadVector2();
            }
            else
            {
                player.PotionOfReturnOriginalUsePosition = null;
                player.PotionOfReturnHomePosition = null;
            }

            player.tryKeepingHoveringUp = bitsByte2[0];
            player.IsVoidVaultEnabled = bitsByte2[1];
            player.sitting.isSitting = bitsByte2[2];
            player.downedDD2EventAnyDifficulty = bitsByte2[3];
            player.isPettingAnimal = bitsByte2[4];
            player.isTheAnimalBeingPetSmall = bitsByte2[5];
            player.tryKeepingHoveringDown = bitsByte2[7];
            player.sleeping.SetIsSleepingAndAdjustPlayerRotation(player, bitsByte3[0]);

            // ゴーストを短距離テレポート
            ShortTeleportGhost(player);

            // ゴーストにタイル情報送信
            RemoteClient.CheckSection(playerIndex, player.position);
        }
    }

    private void ShortTeleportGhost(Player player)
    {
        int playerIndex = player.whoAmI;

        // テレポート相対位置
        Vector2 teleportOffset = Vector2.Zero;
        if (player.controlUp || player.controlJump)
        {
            if (!lastPlayerControl[playerIndex, 0])
            {
                // プレイヤーコントロール入力変化(上入力なし→あり)
                if (lastControlChangedTicks[playerIndex, 0] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
                {
                    teleportOffset.Y -= SHORT_TELEPORT_DISTANCE_Y;
                }
            }
        }
        else
        {
            if (lastPlayerControl[playerIndex, 0])
            {
                // プレイヤーコントロール入力変化(上入力あり→なし)
                lastControlChangedTicks[playerIndex, 0] = currentGameTick;
            }
        }

        if (player.controlLeft)
        {
            if (!lastPlayerControl[playerIndex, 1])
            {
                // プレイヤーコントロール入力変化(左入力なし→あり)
                if (lastControlChangedTicks[playerIndex, 1] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
                {
                    teleportOffset.X -= SHORT_TELEPORT_DISTANCE_X;
                }
            }
        }
        else
        {
            if (lastPlayerControl[playerIndex, 1])
            {
                // プレイヤーコントロール入力変化(上入力あり→なし)
                lastControlChangedTicks[playerIndex, 1] = currentGameTick;
            }
        }

        if (player.controlDown)
        {
            if (!lastPlayerControl[playerIndex, 2])
            {
                // プレイヤーコントロール入力変化(下入力なし→あり)
                if (lastControlChangedTicks[playerIndex, 2] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
                {
                    teleportOffset.Y += SHORT_TELEPORT_DISTANCE_Y;
                }
            }
        }
        else
        {
            if (lastPlayerControl[playerIndex, 2])
            {
                // プレイヤーコントロール入力変化(左入力あり→なし)
                lastControlChangedTicks[playerIndex, 2] = currentGameTick;
            }
        }

        if (player.controlRight)
        {
            if (!lastPlayerControl[playerIndex, 3])
            {
                // プレイヤーコントロール入力変化(右入力なし→あり)
                if (lastControlChangedTicks[playerIndex, 3] + SHORT_TELEPORT_DOUBLE_TAP_TICK > currentGameTick)
                {
                    teleportOffset.X += SHORT_TELEPORT_DISTANCE_X;
                }
            }
        }
        else
        {
            if (lastPlayerControl[playerIndex, 3])
            {
                // プレイヤーコントロール入力変化(右入力あり→なし)
                lastControlChangedTicks[playerIndex, 3] = currentGameTick;
            }
        }

        // プレイヤー入力を記録(パケットが来るまで入力は変化しないため、リセット等は必要ない)
        lastPlayerControl[playerIndex, 0] = player.controlUp || player.controlJump;
        lastPlayerControl[playerIndex, 1] = player.controlLeft;
        lastPlayerControl[playerIndex, 2] = player.controlDown;
        lastPlayerControl[playerIndex, 3] = player.controlRight;

        // 移動距離が0以外の場合
        if (teleportOffset != Vector2.Zero)
        {
            bool isSSC = Main.ServerSideCharacter;
            TSPlayer tsPlayer = TShock.Players[playerIndex];

            // サーバーサイドキャラクターではない場合、サーバーサイドキャラクターに一時的にする
            if (!isSSC)
            {
                Main.ServerSideCharacter = true;
                NetMessage.SendData((int)PacketTypes.WorldInfo, playerIndex, -1, null, 0, 0f, 0f, 0f, 0, 0, 0);
                tsPlayer.IgnoreSSCPackets = true;
            }

            player.position += teleportOffset;

            // プレイヤーがワールド端に行き過ぎないように補正
            if (player.position.X > Main.rightWorld - 992)
            {
                player.position.X = Main.rightWorld - 992;
            }
            if (player.position.X < 992)
            {
                player.position.X = 992;
            }
            if (player.position.Y > Main.bottomWorld - 992)
            {
                player.position.Y = Main.bottomWorld - 992;
            }
            if (player.position.Y < 992)
            {
                player.position.Y = 992;
            }

            // クライアントにプレイヤー情報送信(事実上のテレポート)
            NetMessage.SendData((int)PacketTypes.PlayerUpdate, playerIndex, -1, null, playerIndex);

            // サーバーサイドキャラクターではなかった場合、元に戻す
            if (!isSSC)
            {
                Main.ServerSideCharacter = false;
                NetMessage.SendData((int)PacketTypes.WorldInfo, playerIndex, -1, null, 0, 0f, 0f, 0f, 0, 0, 0);
                tsPlayer.IgnoreSSCPackets = false;
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者(自分)向けWPF(C#,Xaml)Prism,ReactiveProperty記事マップ

初学者(自分)向けWPF(C#,Xaml)Prism,ReactiveProperty記事マップ

これから書いていくものを含め、狙いを明確にするために記す。

連載1.WPF + Prism + ReactivePropertyを使ってMVVMモデルを形成する。

WPF、Prism、ReactivePropertyでMVVMモデルのアプリを作成しているが、一体どの記述がどのFW、ライブラリに依るものわからないので比較する形で深堀していく。

Microsoftへの文句ばかり垂れていても仕様があるまい。

狙い

WPF + Prism + ReactivePropertyを用いたMVVMモデルを作成するための

土台となる知識を身に着ける。

横断的な資料が少なく、差分で見る記事は多い.。基本となるものにしたい。

検証環境

  • OS: WIndows10 Pro
  • Visual Studio 2019 ver 16.9.2
  • .NET Core 3.1
  • WPF
  • Prism ver.8.0.0
  • ReactiveProperty.WPF ver.7.8.1
  • ReactiveProperty ver.7.8.1

  • WPF(C#, .NET)

    • 特徴
    • 文法
      • XAML
      • C#
      • クラス

[公式Doc WPF,.NET][https://docs.microsoft.com/ja-jp/visualstudio/designers/getting-started-with-wpf?view=vs-2019]
[未確認飛行C][https://ufcpp.net/study/csharp/]

  • Prism
    • 特徴
    • Region
    • CustomRegion
    • ViewDiscovery
    • ViewActivationDeactivation
    • Modules -AppConfig
    • Modules -Code
    • Modules -Directory
    • Modules -LoadManual
    • Modules -Xaml
    • ViewModelLocator
    • ChangeConvertion
    • CustomRegistrations
    • UsingDelegateCommands
    • ActiveAwareCommands
    • UsingEventAggregator
    • FilteringEvents
    • RegionContext
    • BasicRegionNavigation
    • VavigationCallback
    • BavigationPartiipation
    • NavigateToExistingViews
    • PassinParameters
    • ConfirmCancelNavigation
    • RegionMemberLifemtime
    • NavigationJournal
    • InvokeCommandAction

[Prism Sample][https://github.com/PrismLibrary/Prism-Samples-Wpf]

  • ReactiveProperty
    • 特徴
    • ReactiveProperty
    • ReadOnlyReactiveProperty
    • ReactivePropertySlim
    • ReadOnlyReactivePropertySlim
    • ReactiveCollection
    • ReadOnlyReactiveCollection
    • ReactiveCommand
    • AsyncReactiveCommand

[RPを編む][https://elf-mission.net/programming/wpf/getting-started-2020/step07/#ReactivePropertySlim]

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

【EPSON製 サーマルプリンター】過剰なロール紙の頭出しをやめさせる方法

これは「C#によるPOSレジ・サーマルプリンター開発入門」と称して連載している記事の1つです。他はこちら

EPSON製のTM-L90型レシートプリンターを何台かヤフオクで購入し使用しているのですが、
その中の1台だけ、電源を入れる度に

「ピーーーーーーーーーー」

という機械音と共に、ロール紙を20cmほど吐き出してくる個体がいました。
毎回毎回、起動する度に貴重なロール紙を20cmも無駄遣いしてくるのは、精神衛生上とても良くありません。

じゃあ直そうと思いEPSON Memory Switch Setting Utilityから頭出しの設定を変更してみるも、
「ピーーーーーーーーーー」
と無情にも20cmのロール紙を無駄遣いされてしまい……

それ以降はもう諦めていたのですが、
先日全く関係ない調べ物をしていたところ、偶然解決法が見つかったのでここにメモを残しておこうと思います。

用紙レイアウトを設定していないことが原因

EPSON Memory Switch Setting Utilityから「プリンタステータス」→「ステータス取得」とボタンを押し、
「用紙レイアウト」タブを確認してみて下さい。
下の画像のように「用紙の種類:指定なし」となっているのではないでしょうか。
用紙レイアウトの設定:変更前

過剰な頭出しの原因は、起動時、自動的に使用しているロール紙がレシート紙なのかラベル紙なのか判別しているからです。

解決法

以下の画像のように、用紙の種類を「指定なし」以外にして「ステータス更新」すればOKです。
用紙レイアウトの設定:変更後

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

vs2010でagauge

概要

vs2010でagaugeやってみた。

環境

windows vista
vs2010
c#

参考にしたページ

写真

無題.jpg

サンプルコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace AGaugeApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void aGauge1_ValueInRangeChanged(object sender, AGauge.ValueInRangeChangedEventArgs e)
        {
            if (e.valueInRange == 0)
            {
                pictureBox1.BackColor = Color.Green;
            }
            else
            {
                pictureBox1.BackColor = Color.Red;
            }
        }
        private void aGauge2_ValueInRangeChanged(object sender, AGauge.ValueInRangeChangedEventArgs e)
        {
            if (e.valueInRange == 1)
            {
                label1.Text="WARNING!--OBJECTS IN MIRROR MAY APPEAR SLOWER THAN THEY USED TO.";
            }
            else if (e.valueInRange==2)
            {
                label1.Text="IF THIS GAUGE DISPLAYS YOUR CURRENT SPEED AND YOU STILL CAN SEE THIS THEN SOMTHING IS WRONG ;-)";
            }
            else
            {
                label1.Text = "";
            }
        }
        private void trackBar1_ValueChanged(object sender, EventArgs e)
        {
            aGauge1.Value = trackBar1.Value;
            aGauge2.Value = trackBar1.Value;
            textBox1.Text = aGauge1.Value.ToString();
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            aGauge3.Value+=0.5f;
            aGauge4.Value += 10;
            if (aGauge3.Value >= 50)
            {
                aGauge3.Value = 0;
            }
            if (aGauge4.Value >= 300)
            {
                aGauge4.Value = -300;
            }
            aGauge5.Value = aGauge3.Value;
            aGauge6.Value = (Single)(((Int32)aGauge6.Value + 49) % 50);
            aGauge7.Value = (Single)(((Int32)aGauge7.Value + 49) % 50);
            aGauge8.Value = (Single)(((Int32)aGauge8.Value + 51) % 50);
            aGauge11.Value = (Single)(((Int32)aGauge11.Value + 9) % 10);
            aGauge9.Value = (Single)(((Int32)aGauge9.Value + 99) % 100);
            aGauge10.Value = (Single)(((Int32)aGauge10.Value + 99) % 100);
            aGauge12.Value = (Single)(((Int32)aGauge12.Value + 99) % 100);
        }
        private void button1_Click(object sender, EventArgs e)
        {            
            if (aGauge1.NeedleType == 0)
            {
                aGauge1.NeedleType = 1;
            }
            else
            {
                aGauge1.NeedleType = 0;
            }
            if (aGauge2.NeedleType == 0)
            {
                aGauge2.NeedleType = 1;
            }
            else
            {
                aGauge2.NeedleType = 0;
            }
            if (aGauge3.NeedleType == 0)
            {
                aGauge3.NeedleType = 1;
            }
            else
            {
                aGauge3.NeedleType = 0;
            }
            if (aGauge4.NeedleType == 0)
            {
                aGauge4.NeedleType = 1;
            }
            else
            {
                aGauge4.NeedleType = 0;
            }
            if (aGauge5.NeedleType == 0)
            {
                aGauge5.NeedleType = 1;
            }
            else
            {
                aGauge5.NeedleType = 0;
            }
        }
    }
}




以上。

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

vs2010でaguage

概要

vs2010でaguageやってみた。

環境

windows vista
vs2010
c#

参考にしたページ

写真

無題.jpg

サンプルコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace AGaugeApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void aGauge1_ValueInRangeChanged(object sender, AGauge.ValueInRangeChangedEventArgs e)
        {
            if (e.valueInRange == 0)
            {
                pictureBox1.BackColor = Color.Green;
            }
            else
            {
                pictureBox1.BackColor = Color.Red;
            }
        }
        private void aGauge2_ValueInRangeChanged(object sender, AGauge.ValueInRangeChangedEventArgs e)
        {
            if (e.valueInRange == 1)
            {
                label1.Text="WARNING!--OBJECTS IN MIRROR MAY APPEAR SLOWER THAN THEY USED TO.";
            }
            else if (e.valueInRange==2)
            {
                label1.Text="IF THIS GAUGE DISPLAYS YOUR CURRENT SPEED AND YOU STILL CAN SEE THIS THEN SOMTHING IS WRONG ;-)";
            }
            else
            {
                label1.Text = "";
            }
        }
        private void trackBar1_ValueChanged(object sender, EventArgs e)
        {
            aGauge1.Value = trackBar1.Value;
            aGauge2.Value = trackBar1.Value;
            textBox1.Text = aGauge1.Value.ToString();
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            aGauge3.Value+=0.5f;
            aGauge4.Value += 10;
            if (aGauge3.Value >= 50)
            {
                aGauge3.Value = 0;
            }
            if (aGauge4.Value >= 300)
            {
                aGauge4.Value = -300;
            }
            aGauge5.Value = aGauge3.Value;
            aGauge6.Value = (Single)(((Int32)aGauge6.Value + 49) % 50);
            aGauge7.Value = (Single)(((Int32)aGauge7.Value + 49) % 50);
            aGauge8.Value = (Single)(((Int32)aGauge8.Value + 51) % 50);
            aGauge11.Value = (Single)(((Int32)aGauge11.Value + 9) % 10);
            aGauge9.Value = (Single)(((Int32)aGauge9.Value + 99) % 100);
            aGauge10.Value = (Single)(((Int32)aGauge10.Value + 99) % 100);
            aGauge12.Value = (Single)(((Int32)aGauge12.Value + 99) % 100);
        }
        private void button1_Click(object sender, EventArgs e)
        {            
            if (aGauge1.NeedleType == 0)
            {
                aGauge1.NeedleType = 1;
            }
            else
            {
                aGauge1.NeedleType = 0;
            }
            if (aGauge2.NeedleType == 0)
            {
                aGauge2.NeedleType = 1;
            }
            else
            {
                aGauge2.NeedleType = 0;
            }
            if (aGauge3.NeedleType == 0)
            {
                aGauge3.NeedleType = 1;
            }
            else
            {
                aGauge3.NeedleType = 0;
            }
            if (aGauge4.NeedleType == 0)
            {
                aGauge4.NeedleType = 1;
            }
            else
            {
                aGauge4.NeedleType = 0;
            }
            if (aGauge5.NeedleType == 0)
            {
                aGauge5.NeedleType = 1;
            }
            else
            {
                aGauge5.NeedleType = 0;
            }
        }
    }
}




以上。

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

C# 9.0 新機能 「init アクセサ (init 専用セッター)」

今更ながらC#9.0から追加されたinitアクセサ(init 専用セッター)を使ってみたのでメモ
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9#init-only-setters

前提

以下をメンバに持つSampleというクラスがあったとします。

  • getアクセサとsetアクセサを持つ通常のプロパティ(Property
  • getアクセサしか持たない get-only(read-only) なプロパティ(GetOnlyProperty
public class Sample
{
    public string Property { get; set; }    // get アクセサと set アクセサ
    public string GetOnlyProperty { get; }  // get アクセサのみ

    public Sample() { }

    public Sample(string property, string getOnlyProperty)
    {
        Property = property;
        GetOnlyProperty = getOnlyProperty;
    }
}

C# 8.0 まで

各プロパティに値を設定したい場合、以下のように
setアクセサを持つプロパティ(Property)はコンストラクタでもオブジェクト初期化子でも、それ以外でも値を設定することができます。

一方で、setアクセサを持たない get-only なプロパティ(GetOnlyProperty)は、コンストラクタでしか値を設定することができません。
もちろん、自クラスのメソッド(SampleMethod)内でも設定することはできません。

public class Sample
{
    public string Property { get; set; }
    public string GetOnlyProperty { get; }

    public Sample() { }

    public Sample(string property, string getOnlyProperty)
    {
        Property = property;
        GetOnlyProperty = getOnlyProperty;
    }

    public void SampleMethod(string property, string getOnlyProperty)
    {
        Property = property;                //OK
        GetOnlyProperty = getOnlyProperty;  //Error
    }
}

public class Program
{
    private static void Main(string[] args)
    {
        //コンストラクタで値を設定
        var s1 = new Sample("foo", "bar"); //OK

        //値を設定
        s1.Property = "baz";        //OK
        s1.GetOnlyProperty = "baz"; //Error

        //オブジェクト初期化子で値を設定
        var s2 = new Sample
        {
            Property = "foo",           //OK
            GetOnlyProperty = "bar",    //Error
        };
    }
}

コンストラクタでの初期化以降、値を書き換えられることが無い get-only なプロパティ(GetOnlyProperty)は、安全に利用することができますが、少々制限が厳しすぎると感じていた方もいらっしゃるのではないでしょうか。

具体的には、オブジェクト初期化子でも値を設定することができないという点です。

C# 8.0 までは 初期化処理(コンストラクタ及びオブジェクト初期化子)では値が設定できるが
それ以外は値を設定することができないプロパティを定義することはできません。

C# 9.0

C# 9.0 で導入されたinitアクセサ を利用すると、
上述した通り、C# 8.0 までは実現することができなかった
初期化処理(コンストラクタ及びオブジェクト初期化子)では値が設定できるが
それ以外は値を設定することができないプロパティを定義することができます。

get-onlyなプロパティに対してinit-onlyなプロパティと呼びます。

定義方法は非常に簡単で、setの代わりにinitと記述するだけです。

public string InitOnlyProperty { get; init; }

get-only なプロパティとの主な違いは
init-only プロパティでは、オブジェクト初期化子で値を設定することが出来るという点です。

もちろん get-only なプロパティ同様に、コンストラクタでも値を設定することができますし、
それ以外の場所では値を設定することができません。
(他のinitアクセサ内は除く)

initアクセサを利用することによって、 get-only なプロパティよりも柔軟に利用できるプロパティが定義できるようになりました。

public class Sample
{
    public string Property { get; set; }
    public string GetOnlyProperty { get; }
    public string InitOnlyProperty { get; init; }

    public Sample() { }

    public Sample(string property, string getOnlyProperty, string initOnlyProperty)
    {
        Property = property;                   //OK
        GetOnlyProperty = getOnlyProperty;     //OK
        InitOnlyProperty = initOnlyProperty;   //OK
    }

    public void SampleMethod(string property, string getOnlyProperty, string initOnlyProperty)
    {
        Property = property;                    //OK
        GetOnlyProperty = getOnlyProperty;      //Error
        InitOnlyProperty = initOnlyProperty;    //Error
    }
}

public class Program
{
    private static void Main(string[] args)
    {
        //コンストラクタで値を設定
        var s1 = new Sample("foo", "bar", "baz");

        //値を設定
        s1.Property = "qux";            //OK
        s1.GetOnlyProperty = "qux";     //Error
        s1.InitOnlyProperty = "qux";    //Error

        //オブジェクト初期化子で値を設定
        // get-only プロパティでは値を設定できないが、 init-only プロパティでは可能
        var s2 = new Sample
        {
            Property = "foo",           //OK
            GetOnlyProperty = "bar",    //Error
            InitOnlyProperty = "baz",       //OK
        };
    }
}

以上となります。

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

C# 9.0 新機能 「レコード(record)型 / with 式」

今更ながらC#9.0から追加されたレコード型とwith式を使ってみたのでメモ
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/tutorials/records

前提

例えば、平面上の座標を示すPoint2Dクラスのインスタンスが複数存在する時に、
あるインスタンスが他のインスタンスと内容(X座標とY座標の値)が等しいかを判定したいとします。

public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
    }
}

レコードを利用しない場合(~C#8.0)

参照先ではなく、内容が等しいかを判定するためには
C#8.0まではPoint2DクラスでEqualsメソッドとGetHashCodeメソッドをオーバーライドする必要がありました。

public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);

    //X座標とY座標の値で等価比較をするようにオーバーライド
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType()) return false;
        var p = (Point2D)obj;
        return ReferenceEquals(this, p) || 
               X == p.X && Y == p.Y;
    }

    //X座標とY座標の値からハッシュ値を生成するようにオーバーライド
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1):{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2):{src.Equals(p2)}"); // True
    }
}

レコードを利用した場合(C#9.0~)

C#9.0からはレコード型を利用することによって、より簡単に実装することができます。
レコードを定義するには、classの代わりにrecordキーワードを使用して型を宣言します。

 public record Point2D(int X, int Y);

レコードは、クラスと同じように扱うことができます。もちろんメンバを定義することもできます。

public record Point2D(int X, int Y)
{
    public int Property { get; }

    public Point2D(int x, int y, int property) 
        : this(x, y)
    {
        Property = property;
    }

    public void Method()
    {
        Console.WriteLine("foo");
    }
}

そして、主に以下の作業を内部的に実施してくれます。

  • 型名に続く()で渡した内容で、get/initなプロパティを生成
    • public int X { get; init; }
    • public int Y { get; init; }
  • 各プロパティの値を引数に取るコンストラクタの生成
  • Equalsメソッドをオーバーライド
    • 各プロパティの値(ここではX座標とY座標の値)で等価比較をする
  • GetHashCodeメソッドをオーバーライド
    • 各プロパティの値からハッシュ値を生成する
  • ToStringメソッドをオーバーライド
    • レコード名 { プロパティ名 = 値, ... }の形式で文字列化する

結果として、あるインスタンスが他のインスタンスと内容が等しいかを判定したい時にレコード型を利用すると、今までと比べて簡単に実装することが可能です。

public record Point2D(int X, int Y);

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1):{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2):{src.Equals(p2)}"); // True

        //ToStringもオーバーライドされている
        Console.WriteLine(src); //Point2D { X = 10, Y = 20 }
    }
}

レコードの複製(with 式)

レコード型と同じくC#9.0から追加されたwith式を使うと、簡単にレコードの複製をすることができます。

コピー先 = コピー元 with { プロパティ名 = 値, ... }

public record Point2D(int X, int Y);

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var copy = src with { X = 30, Y = 60 };

        Console.WriteLine(copy); // Point2D { X = 30, Y = 60 }
        Console.WriteLine(src);  // Point2D { X = 10, Y = 20 }
    }
}

以上です。

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

C# 9.0 「レコード(record)型」

今更ながらC#9.0から追加されたレコード型を使ってみたのでメモ
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/tutorials/records

前提

例えば、平面上の座標を示すPoint2Dクラスのインスタンスが複数存在する時に、
あるインスタンスが他のインスタンスと内容(X座標とY座標の値)が等しいかを判定したいとします。

public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
    }
}

レコードを利用しない場合(~C#8.0)

参照先ではなく、内容が等しいかを判定するためには
C#8.0まではPoint2DクラスでEqualsメソッドとGetHashCodeメソッドをオーバーライドする必要がありました。

public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);

    //X座標とY座標の値で等価比較をするようにオーバーライド
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType()) return false;
        var p = (Point2D)obj;
        return ReferenceEquals(this, p) || 
               X == p.X && Y == p.Y;
    }

    //X座標とY座標の値からハッシュ値を生成するようにオーバーライド
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1):{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2):{src.Equals(p2)}"); // True
    }
}

レコードを利用した場合(C#9.0~)

C#9.0からはレコード型を利用することによって、より簡単に実装することができます。
レコードを定義するには、classの代わりにrecordキーワードを使用して型を宣言します。

 public record Point2D(int X, int Y);

レコードは、クラスと同じように扱うことができます。
そして、主に以下の作業を内部的に実施してくれます。

  • 型名に続く()で渡した内容で、get/initなプロパティを生成
    • public int X { get; init; }
    • public int Y { get; init; }
  • 各プロパティの値を引数に取るコンストラクタの生成
  • Equalsメソッドをオーバーライド
    • 各プロパティの値(ここではX座標とY座標の値)で等価比較をする
  • GetHashCodeメソッドをオーバーライド
    • 各プロパティの値からハッシュ値を生成する
  • ToStringメソッドをオーバーライド
    • クラス名 { プロパティ名 = 値, ... }の形式で文字列化する

結果として、あるインスタンスが他のインスタンスと内容が等しいかを判定したい時にレコード型を利用すると、今までと比べて簡単に実装することが可能です。

public record Point2D(int X, int Y);

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1):{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2):{src.Equals(p2)}"); // True

        //ToStringもオーバーライドされている
        Console.WriteLine(src.ToString()); //Point2D { X = 10, Y = 20 }
    }
}

以上です。

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

C# 9.0 新機能 「レコード(record)型」

今更ながらC#9.0から追加されたレコード型を使ってみたのでメモ
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/tutorials/records

前提

例えば、平面上の座標を示すPoint2Dクラスのインスタンスが複数存在する時に、
あるインスタンスが他のインスタンスと内容(X座標とY座標の値)が等しいかを判定したいとします。

public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
    }
}

レコードを利用しない場合(~C#8.0)

参照先ではなく、内容が等しいかを判定するためには
C#8.0まではPoint2DクラスでEqualsメソッドとGetHashCodeメソッドをオーバーライドする必要がありました。

public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);

    //X座標とY座標の値で等価比較をするようにオーバーライド
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType()) return false;
        var p = (Point2D)obj;
        return ReferenceEquals(this, p) || 
               X == p.X && Y == p.Y;
    }

    //X座標とY座標の値からハッシュ値を生成するようにオーバーライド
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1):{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2):{src.Equals(p2)}"); // True
    }
}

レコードを利用した場合(C#9.0~)

C#9.0からはレコード型を利用することによって、より簡単に実装することができます。
レコードを定義するには、classの代わりにrecordキーワードを使用して型を宣言します。

 public record Point2D(int X, int Y);

レコードは、クラスと同じように扱うことができます。
そして、主に以下の作業を内部的に実施してくれます。

  • 型名に続く()で渡した内容で、get/initなプロパティを生成
    • public int X { get; init; }
    • public int Y { get; init; }
  • 各プロパティの値を引数に取るコンストラクタの生成
  • Equalsメソッドをオーバーライド
    • 各プロパティの値(ここではX座標とY座標の値)で等価比較をする
  • GetHashCodeメソッドをオーバーライド
    • 各プロパティの値からハッシュ値を生成する
  • ToStringメソッドをオーバーライド
    • クラス名 { プロパティ名 = 値, ... }の形式で文字列化する

結果として、あるインスタンスが他のインスタンスと内容が等しいかを判定したい時にレコード型を利用すると、今までと比べて簡単に実装することが可能です。

public record Point2D(int X, int Y);

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容(X座標とY座標の値)が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1):{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2):{src.Equals(p2)}"); // True

        //ToStringもオーバーライドされている
        Console.WriteLine(src.ToString()); //Point2D { X = 10, Y = 20 }
    }
}

以上です。

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

appsettings.jsonをReleaseやDebugで切り替える

AWSのElastic BeanstalkにASP.NET Core アプリケーションをデプロイする際、appsettings.jsonの切り替えが意外にも困ったのでまとめ。

appsettings.jsonとappsettings.Development.json、その他appsettings.xxxx.jsonなど「実行環境」の選択でappsettingsを切り替えることは簡単だが、ReleaseやDebugなど「ビルド構成」で切り替える機能は.NET Coreのアプリにはない。
いろんな方法があると思うが、csprojにmsbuildのコピーコマンドを手で追記することで、シンプルにビルド後にビルド後のディレクトリ内でappsettings.jsonをappsettings.xxx.jsonで上書きすることができた。

sample.csproj
  <Target Name="CopyStaging" AfterTargets="Build">
    <Copy SourceFiles="$(TargetDir)\settings\appsettings.$(Configuration).json" DestinationFiles="$(TargetDir)\appsettings.json" SkipUnchangedFiles="false" />
  </Target>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

光学ドライブをEjectするCLIプログラムを作った


はじめに

Microsoft-WindowsMediaPlayer(WMP)のCOMコンポーネントを呼び出すことで、光学ドライブをEjectするCUIなプログラムを作った。


なぜ作った?

そこに、Eject APIがあったからさ


使い方

CD-ROMドライブからディスクをEjectするCUIなプログラム」から、ダウンロードし、zipファイルを解凍して、出てきた「sEjectCDRom.NET2.exe/sEjectCDRom.NET4.exe」を実行するだけ。

  • .NET Framework2.0 用 : sEjectCDRom.NET2.exe
  • .NET Framework4.0 用 : sEjectCDRom.NET4.exe

使い方(詳細)

引数なしで、
c:\>sEjectCDRom.NET2.exe
or
c:\>sEjectCDRom.NET4.exe
とすると、接続されている全ての光学ドライブをEjectする

例えば、以下のような引数で実行すると、

c:\>sEjectCDRom.NET2.exe d q
or
c:\>sEjectCDRom.NET4.exe d q

Dドライブと、QドライブだけをEjectする

引数は何個でも指定できる。


使用上の注意

Ejectはできるが、Insertはできない。


以上

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