20201123のC#に関する記事は7件です。

C#でファイル暗号化・復号化ツールを作ってみた

GitHubに上げています。

ただ、勉強用に作成したものなので、実務などでは使用しないで下さい
暗号化処理の実装とか、バイナリファイルの読込・書込、タプルやディクショナリを勉強したり、GitHubへの反映の仕方を知りたかったのも動機です。

開発環境は以下の通りです。

  • NET 5.0
  • C#
  • Visual Studio Community for Mac
  • macOS Mojave

個人的に気になったところ

Dictionary初期化の書き方

こんな風に書く。

private static Dictionary<string, string> argConst = new Dictionary<string, string>
{
    { "key1", "value1" },
    { "key2", "value2" }
};

StreamReader, StreamWriterはテキストの読み書きに使う

.NETのドキュメントに書いてある通り、それぞれ基底クラスがTextReaderTextWriterという名前の通り、テキストを扱う。

バイナリファイルは、BinaryReaderBinaryWriterを使う。この辺りで結構はまった。

BinaryWriterだと以下のように書く。

//write decrypted data to file
using (BinaryWriter binaryWriter = new BinaryWriter(new FileStream(outputFile, FileMode.Create)))
{
    foreach (byte b in byteArray)
    binaryWriter.Write(b);
}

CryptoStreamを使用している場合、BinaryReaderのLengthが取れなかった。seekができないようで、要するに今どの場所を読み取っているかという情報が取れないらしい。

なので、復号化処理で入力データのseekが終了しているかどうかは、EndOfStreamExceptionの発生を例外処理で補足する方法しかできなかった。

あと、Rijndael = AESという認識だったので、Rijndaelクラスを使用したが、Aesクラスもあるようで、Microsoftのドキュメント(英語)では後者を使用すべきと書かれている…。

You should use the Aes algorithm instead of Rijndael.

Rijndael Class

ちなみに、日本語サイトは機械翻訳のようで、意味が全然理解できないくらい文章が変でした。

まぁ今回はC#の自習でしたが、実務で暗号化が必要な時は、基本的には自作しない方が無難だと思います。

以上です。

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

.NET(C#)でファイル暗号化・復号化ツールを作ってみた

GitHubに上げています。

ただ、勉強用に作成したものなので、実務などでは使用しないで下さい
暗号化処理の実装とか、バイナリファイルの読込・書込、タプルやディクショナリを勉強したり、GitHubへの反映の仕方を知りたかったのも動機です。

開発環境は以下の通りです。

  • NET 5.0
  • C#
  • Visual Studio Community for Mac
  • macOS Mojave

個人的に気になったところ

Dictionary初期化の書き方

こんな風に書く。

private static Dictionary<string, string> argConst = new Dictionary<string, string>
{
    { "key1", "value1" },
    { "key2", "value2" }
};

StreamReader, StreamWriterはテキストの読み書きに使う

.NETのドキュメントに書いてある通り、それぞれ基底クラスがTextReaderTextWriterという名前の通り、テキストを扱う。

バイナリファイルは、BinaryReaderBinaryWriterを使う。この辺りで結構はまった。

BinaryWriterだと以下のように書く。

//write decrypted data to file
using (BinaryWriter binaryWriter = new BinaryWriter(new FileStream(outputFile, FileMode.Create)))
{
    foreach (byte b in byteArray)
    binaryWriter.Write(b);
}

CryptoStreamを使用している場合、BinaryReaderのLengthが取れなかった。seekができないようで、要するに今どの場所を読み取っているかという情報が取れないらしい。

なので、復号化処理で入力データのseekが終了しているかどうかは、EndOfStreamExceptionの発生を例外処理で補足する方法しかできなかった。

あと、Rijndael = AESという認識だったので、Rijndaelクラスを使用したが、Aesクラスもあるようで、Microsoftのドキュメント(英語)では後者を使用すべきと書かれている…。

You should use the Aes algorithm instead of Rijndael.

Rijndael Class

ちなみに、日本語サイトは機械翻訳のようで、意味が全然理解できないくらい文章が変でした。

まぁ今回はC#の自習でしたが、実務で暗号化が必要な時は、基本的には自作しない方が無難だと思います。

以上です。

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

.NET(C#)でファイル暗号化・復号化ツールを作ったのでメモ

GitHubに上げています。

ただ、勉強用に作成したものなので、実務などでは使用しないで下さい
暗号化処理の実装とか、バイナリファイルの読込・書込、タプルやディクショナリを勉強したり、GitHubへの反映の仕方を知りたかったのも動機です。

開発環境は以下の通りです。

  • NET 5.0
  • C#
  • Visual Studio Community for Mac
  • macOS Mojave

・更新履歴
11/23 新規公開
11/25 リファクタリングの結果などを追記

個人的に気になったところ

Dictionary初期化の書き方

こんな風に書く。

private static Dictionary<string, string> argConst = new Dictionary<string, string>
{
    { "key1", "value1" },
    { "key2", "value2" }
};

StreamReader, StreamWriterはテキストの読み書きに使う

.NETのドキュメントに書いてある通り、それぞれ基底クラスがTextReaderTextWriterという名前の通り、テキストを扱う。

バイナリファイルは、BinaryReaderBinaryWriterを使う。この辺りで結構はまった。

BinaryWriterだと以下のように書く。

//write decrypted data to file
using (BinaryWriter binaryWriter = new BinaryWriter(new FileStream(outputFile, FileMode.Create)))
{
    foreach (byte b in byteArray)
    binaryWriter.Write(b);
}

CryptoStreamを使用している場合、BinaryReaderのLengthが取れなかった。seekができないようで、要するに今どの場所を読み取っているかという情報が取れないらしい。

なので、復号化処理で入力データのseekが終了しているかどうかは、EndOfStreamExceptionの発生を例外処理で補足する方法しかできなかった。
→ CopyToを使うことで一応回避(但し、メモリを使うので注意)。
あと、usingをすっきりさせた。(2020/11/25追記)

using (FileStream   fileStream   = new FileStream(inputFile, FileMode.Open))
using (CryptoStream cryptoStream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read))
using (MemoryStream memory       = new MemoryStream())
using (BinaryWriter binaryWriter = new BinaryWriter(new FileStream(outputFile, FileMode.Create)))
    {
        //Read decrypted data into memory
        cryptoStream.CopyTo(memory);
        byte[] decryptBytes = memory.ToArray();

        //write decrypted data to file
        binaryWriter.Write(decryptBytes);

    }

Rijndael = AESという認識だったので、Rijndaelクラスを使用したが、Aesクラスもあるようで、Microsoftのドキュメント(英語)では後者を使用すべきと書かれている…。

You should use the Aes algorithm instead of Rijndael.

Rijndael Class

実際、MSのドキュメントでは、Aes aes = Aes.Create()のように、Aes.CreateのFactoryが使用されている。

ちなみに、日本語サイトは機械翻訳のようで、意味が全然理解できないくらい文章が変でした。

まぁ今回はC#の自習でしたが、実務で暗号化が必要な時は、基本的には自作しない方が無難だと思います。

以上です。

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

【Unity】打ち上げ攻撃→空中コンボ攻撃を実装

はじめに

初めてQiitaに記事を投稿します。

現在Unityで個人開発している2.5Dのアクションゲームで打ち上げ攻撃からの空中コンボという
ハイスピードなアクションゲームによくみられる挙動を実装したのですが、
実装するにあたって海外の記事や動画でもそれらしい情報がほとんど見つけられず
苦戦したのでこの度自分なりの実装方法を記事として残すことにしました。

本記事ではRigidBodyを用いてキャラクターを操作する方法を用いています。
CharacterControllerを使った場合でも応用は効くかと思います。
アニメーションの再生やアニメーターを使ったアニメーションの繋ぎ方等は省略しておりますので
下記のサイトを参考にしていただければと思います。
[Unityのアクションゲームで連続攻撃を実現する方法](https://gametukurikata.com/program/continuityattack)

それでは早速本題に入っていきましょう!

目次

・打ち上げ攻撃の実装
・空中コンボの制御
・最後に

打ち上げ攻撃の実装

まずは一連の動作の起点となる打ち上げ攻撃の実装になります。
これがなきゃ始まりません。

と言ってもこの時点では複雑なことはなく、打ち上げ攻撃のアニメ―ションイベントで
操作キャラと敵を打ち上げる関数を呼び出すだけです。
打ち上げ動作に移るイイ感じのフレームで呼び出してあげましょう。
(アニメーションイベントに関してはこちらを)
https://docs.unity3d.com/ja/2018.4/Manual/animeditor-AnimationEvents.html)

以下、打ち上げ用の関数です。Dotweenを用いています。
打ち上げる高さや速度に関してはゲームバランスにうまくハマるように各々調整していただければと思います。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;


public class MeleeAttackManager : MonoBehaviour
    {
        Rigidbody rb;

        void Start()
        {
            rb = GetComponent<Rigidbody>();
        }

        void Launch() //打ち上げ用の関数
        {
            rb.DOMoveY(7f, 0.5f); //Y方向に0.5秒かけて上昇
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().Launch(); //攻撃を受けた敵を打ち上げます。
            }
        }

同様に敵キャラにも打ち上げ用の関数の書かれたスクリプトをアタッチしましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EnemyScript : MonoBehaviour
{
    Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
    }


    public void Launch()
    {
        rb.DOMoveY(7f, 0.5f); //攻撃を受けた敵を打ち上げます。
    }
}

打ちあがる高さと時間に関してはプレイヤー側と同じ数値に揃えましょう。

これで打ち上げ処理は実装できました!
ところがこのままでは。。。
ezgif.com-gif-maker.gif
空中でコンボを決めようにも落下してしまっています。
カッコよさもへったくれもありません。
なので次は空中コンボの制御をしていきましょう。

空中コンボの制御

打ち上げ攻撃を実装できましたがこのままでは空中コンボに落下してしまいみっともないです。
ケレン味のあるアクションを実現するためにも重力に逆らってもらいましょう。

方法としては
打ち上げ→RigidBodyのUseGravityをオフにしたり同じくRigidBodyのDragの数値を弄ることで一定時間空中に留まる→空中コンボが敵に当たれば滞空時間を延長→攻撃が当たらなかったりコンボが途切れると落下を始める

という実装方法に落ち着きました。
今回はDragの値を弄ることにします。
早速詳しくやっていきましょう。

まずは滞空するためにDragの値を弄る関数を用意してこの関数を打ち上げ用の関数実行時に呼び出します。

  public void OffGrvity()
        {
            rb.drag = 40; //RigidBodyのDragの数値を弄る
        }

    void Launch()
        {
            OffGrvity(); //OffGrvityを実行
            rb.DOMoveY(7f, 0.5f);
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().Launch(); //敵を打ち上げる
                enemy.GetComponent<EnemyScript>().OffGrvity(); //敵側のOffGravityを実行
            }
        }

Dragの値は40にして殆ど落下しないようになっています。
同様に敵側にも

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EnemyScript : MonoBehaviour
{
    Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
    }
  
  public void OffGrvity()
    {
        rb.drag = 40; //RigidBodyのDragの数値を弄る
    }

    public void Launch()
    {
        rb.DOMoveY(7f, 0.5f); //攻撃を受けた敵を打ち上げます。
    }
}

Dragの値を弄る関数を追加して攻撃を受けた際に実行されるようにします。
これで落下せずに空中コンボを決められますがこのままでは宙に浮いたまま降りてこないので

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;


public class MeleeAttackManager : MonoBehaviour
{
        Rigidbody rb;
        private float airStayTime = 1; //滞空可能時間

        void Start()
        {
            rb = GetComponent<Rigidbody>();
        }

    void Update()
        {

            if (rb.drag != 0)
            {
                airStayTime -= Time.deltaTime; //Dragの値が0でなければairStayTimeの値を減らしていく
            }

            if (airStayTime < 0)
            {
                OnGrvity();  //落下
                airStayTime = 1; //airStayTimeの値をリセット
            }
        }

    public void OnGrvity()
        {
            if(rb.drag != 0)
            {
                rb.drag = 0; //Dragの値を0に戻して落下
            }
        }

    public void OffGrvity()
        {
            rb.drag = 40; //RigidBodyのDragの数値を弄る
        }

        void Launch() //打ち上げ用の関数
        {
            rb.DOMoveY(7f, 0.5f); //Y方向に0.5秒かけて上昇
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().Launch(); //攻撃を受けた敵を打ち上げます。
            }
        }

        public void airStayExtend() //滞空時間を延長
        {
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().airStay(); //敵のairStayTimeの値を更新して滞空時間を延長
            }
            airStayTime= 0.7f; //airStayTimeの値を更新して滞空時間を延長
        }
}

変数airStayTimeを新たに用意します。
Dragの値が0でなかった場合Update内でairStayTimeの値を減らし、
airStayTimeの値が0を下回った時点でOnGravity関数を実行して落下しています。
さらに、空中で攻撃を当てるたびにairStayTimeの値を増やしていかないと
コンボ中にやっぱり落下してしまうので
airStayExtendという関数を用意しています。
このairStayExtendを空中コンボのアニメーションイベントで実行することで
敵に攻撃を当てた際に滞空時間を延長しています。

例によって例の如く敵のスクリプトにも同じ処理を

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EnemyScript : MonoBehaviour
{
    Rigidbody rb;
  private float airStayTime = 1;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
    if(rb.drag != 0)
        {
            airStayTime -= Time.deltaTime;
        }

        if(airStayTime < 0)
        {
            OnGrvity();
            airStayTime = 1;
        }
    }

  public void OnGrvity()
        {
            if(rb.drag != 0)
            {
                rb.drag = 0; //Dragの値を0に戻して落下
            }
        }
  
  public void OffGrvity()
    {
        rb.drag = 40; //RigidBodyのDragの数値を弄る
    }

    public void Launch()
    {
        rb.DOMoveY(7f, 0.5f); //攻撃を受けた敵を打ち上げます。
    }

  public void airStayExtend()
    {
        airStayTime = 0.7f; //滞空時間を延長
    }
}

これで空中コンボは完成です!

ezgif.com-gif-maker (1).gif

最後に

突貫工事で実装をしたので粗がある気はしますが、また改善され次第更新していこうと思います。
Qiita初投稿で読みづらい部分等あったかもしれませんが最後まで読んでいただきありがとうございました!

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

【C#】「参照型」を渡しても「参照渡し」にはならない件

はじめに

参照型とか参照渡しとかごっちゃになりそうだったので自分なりに整理してみた結果、表題の通り「『参照型』を渡したからといって、『参照渡し』にはなっていない」ということに気づきました。
以下に整理してみます。

「参照型」と「参照渡し」は違う

クラスなどの「参照型」ならば「参照渡し」になると思っていませんか?
というか僕は思っていたんですけどね。全く違いました。

「参照型」とは、変数が「インスタンスへの参照」を示す型のことです。
「参照渡し」とは、渡したいもの1の「参照」を渡す渡し方のことです。
「参照型」とは「型の性質」であり、「参照渡し」とは「インスタンスの引き渡し方法」なので、それらは互いに関係し合いません。
なので当然、「参照型」ならば「参照渡し」である、とはならないわけです。

「値型」と「値渡し」

対するものとして、「値型」と「値渡し」があります。
「値型」とは、変数が「インスタンスそのもの」を示す型のことです。
「値渡し」とは、渡したいもの1の「コピー」を渡す渡し方のことです。
そして、C#では基本的に「参照渡し」ではなく「値渡し」となります。
したがって、「参照型」のインスタンスを普通に渡した場合、「参照型の値渡し」となります。参照型だからといって、参照渡しにはなりません。

参照型の「値渡し」

「参照型の値渡し」とはどういうことかを上の説明に当てはめて考えてみると、《「変数が『インスタンスへの参照』を示す型」の変数を、「渡したいもののコピーを渡す方法で渡す」》ということになります。
この場合、「渡したいもの」とは「インスタンスへの参照」となりますので、「インスタンスへの参照のコピーを渡す」ということになりますね。

参照型の値渡し.png

※本来、参照型の実体はヒープに置かれますがわかりやすくするためクラスAの中に書いています

参照型の「参照渡し」

C#では基本的に値渡しとなるわけですが、御存知の通りref修飾子を使えば「参照渡し」をすることもできます。
では、「参照型」に対して「参照渡し」、つまり「参照型の参照渡し」をするとどうなるのでしょうか?

また上の説明に当てはめて考えてみると、《「変数が『インスタンスへの参照』を示す型」の変数を、「『渡したいものの参照』を渡す方法で渡す」》ということになります。
この場合、「渡したいもの」とは「インスタンスへの参照」なので、「インスタンスへの参照の参照を渡す」ということになります。

参照型の参照渡し.png

つまり、もともと「インスタンスへの参照」だったものへのさらに深い2重参照をしていることになります。

このように、「参照型」でも「値渡し」「参照渡し」の2通りの渡し方で渡すことができ、それぞれ内部で異なる処理がされていることがわかりました。
では、「参照型を値渡しした場合」と、「参照型を参照渡しした場合」とで、実際の挙動はどう違うのでしょうか?

「参照型の値渡し」と「参照型の参照渡し」の挙動の違い

「参照型の値渡し」と「参照型の参照渡し」との違いとしては、渡った先でインスタンス自体を書き換えられるかどうかが挙げられます。

「参照型の値渡し」では、「インスタンスへの参照のコピー」が渡されるのでした。
参照とはいえコピーなので、それ自体を別のものに書き換えたところで、コピー元には何も影響がありません。

しかし、「参照型の参照渡し」では、「インスタンスへの参照の参照」が渡されるのでした。
渡されているものは「参照」なので、それを別のものに書き換えれば、当然元データもそのものに書き換えられてしまいます。

これが「参照型の値渡し」と「参照型の参照渡し」との挙動の違いになります。
では、実際にどのように挙動の違いがあるか試してみます。

挙動の違いを実際に試して実感する

「参照型の値渡し」と「参照型の参照渡し」との挙動の違いを、以下のソースコードで試してみます。
比較のために、「値型の値渡し」と「値型の参照渡し」も同時に挙動の違いを確認します。

class Program
{
    static void Main(string[] args)
    {
        //参照型と値型のインスタンスを4つずつ用意
        var vals = Enumerable.Repeat(0, 4)
                             .Select(_ => new ValueStruct("初期値"))
                             .ToArray();
        var refs = Enumerable.Repeat(0, 4)
                             .Select(_ => new ReferenceClass("初期値"))
                             .ToArray();

        //値型を値渡しでプロパティ書き換え
        OverwriteProperty(vals[0]);
        //値型を参照渡しでプロパティ書き換え
        OverwriteProperty(ref vals[1]);
        //値型を値渡しでインスタンス置き換え
        ReplaceInstance(vals[2]);
        //値型を参照渡しでインスタンス置き換え
        ReplaceInstance(ref vals[3]);

        //参照型を値渡しでプロパティ書き換え
        OverwriteProperty(refs[0]);
        //参照型を参照渡しでプロパティ書き換え
        OverwriteProperty(ref refs[1]);
        //参照型を値渡しでインスタンス置き換え
        ReplaceInstance(refs[2]);
        //参照型を参照渡しでインスタンス置き換え
        ReplaceInstance(ref refs[3]);

        foreach (var item in vals)
        {
            Console.WriteLine(item.Message);
        }
        foreach (var item in refs)
        {
            Console.WriteLine(item.Message);
        }

        Console.ReadKey();
    }

    static void OverwriteProperty(ValueStruct @struct)
    {
        @struct.Message = "値型を値渡しでプロパティ書き換えました";
    }
    static void OverwriteProperty(ref ValueStruct @struct)
    {
        @struct.Message = "値型を参照渡しでプロパティ書き換えました";
    }
    static void OverwriteProperty(ReferenceClass @class)
    {
        @class.Message = "参照型を値渡しでプロパティ書き換えました";
    }
    static void OverwriteProperty(ref ReferenceClass @class)
    {
        @class.Message = "参照型を参照渡しでプロパティ書き換えました";
    }

    static void ReplaceInstance(ValueStruct @struct)
    {
        @struct = new ValueStruct("値型を値渡しでインスタンス置き換えました");
    }
    static void ReplaceInstance(ref ValueStruct @struct)
    {
        @struct = new ValueStruct("値型を参照渡しでインスタンス置き換えました");
    }
    static void ReplaceInstance(ReferenceClass @class)
    {
        @class = new ReferenceClass("参照型を値渡しでインスタンス置き換えました");
    }
    static void ReplaceInstance(ref ReferenceClass @class)
    {
        @class = new ReferenceClass("参照型を参照渡しでインスタンス置き換えました");
    }
}

/// <summary>
/// 参照型
/// </summary>
class ReferenceClass
{
    public ReferenceClass(string message)
    {
        this.Message = message;
    }
    public string Message { get; set; }
}
/// <summary>
/// 値型
/// </summary>
struct ValueStruct
{
    public ValueStruct(string message)
    {
        this.Message = message;
    }
    public string Message { get; set; }
}

このソースコードが何をしているかというと、①「インスタンスの中身(プロパティ)の書き換え」と②「インスタンス自体の書き換え」という2つの操作を、「値型の値渡し」「値型の参照渡し」「参照型の値渡し」「参照型の参照渡し」という4種類の渡し方で試して、元データにどのような影響があるかを確認しています。

結果は以下のようになりました。「初期値」となっているものは、元データへの影響がないことを示しています。

初期値
値型を参照渡しでプロパティ書き換えました
初期値
値型を参照渡しでインスタンス置き換えました
参照型を値渡しでプロパティ書き換えました
参照型を参照渡しでプロパティ書き換えました
初期値
参照型を参照渡しでインスタンス置き換えました

この結果をまとめると以下のようになります。
※✕が書き換えできなかったことを示します

プロパティ書き換え インスタンス置き換え
値型 値渡し
値型 参照渡し
参照型 値渡し
参照型 参照渡し

値型の場合は想像通りですね。値渡しをすれば、渡った先で何をされようが元データには影響ありません。コピーが渡っているからですね。

参照型の場合は、値型と違って値渡しでもプロパティの書き換えが可能です。しかし、値渡しではインスタンス自体の書き換えはできなくなっています
これは、「値渡し」とはあくまでも「渡したいもののコピー」を渡す渡し方なので、「参照のコピー」に対して別のものに書き換えを行ったところで、「元の参照」には何も影響がないためです。
「参照型の参照渡し」を行うと、「渡したいものの参照」を渡すので、「参照の参照」が引き渡されるため、引き渡し先でインスタンス自体を書き換えると、元の参照にも影響が及ぶというわけですね。

さいごに

参照型と参照渡しの関係性について、ごちゃごちゃになっていたので自分なりに整理してみました。
情報が間違っていたらご指摘等お待ちしています。

[11/23追記]
早速ご指摘いただきました。
「変数」のことを「インスタンス」と表現してしまっていました。お詫びして修正します。


  1. 分かりやすくするため、「渡したいもの」と表現していますが、参照型の場合は 

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

Windows Forms で PerMonitorV2 設定して動きを見てみた

追記

最初 ListBox での動きが変だったのですが .NET Framework 4.7 系から .NET Framework 4.8 に上げたらなおりました。

試したこと

  • Windows Forms アプリを新規作成して app.manifest を追加
  • supportedOS タグの Windows 10 のコメントアウトを外す
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 10 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
    </application>
</compatibility>
  • App.config で PerMoniterV2 の設定を追加
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <System.Windows.Forms.ApplicationConfigurationSection>
        <add key="DpiAwareness" value="PerMonitorV2" />
    </System.Windows.Forms.ApplicationConfigurationSection>
</configuration>
  • Form のフォントを Yu Gothic UI, 9pt に変更
  • Form の AutoScaleMode を Font から Dpi に変更
  • ListBox や DataGridView なんかを置いた画面を適当に作成
  • デバッガーにアタッチしない状態で実行して動きを確認

結果

ListBox のある画面

拡大率 100% の画面

image.png

150% に持って行った状態

image.png

100% に再度移動させた状態

image.png

なんか 100% → 150% → 100% に持っていったら ListBox のフォントが大きくなった…

後述しますが、この変な動きは .NET Framework 4.7.2 で起きていて .NET Framework 4.8 にすると、この変な動きは治っていました。

DataGridView

100% で表示

image.png

150% に移動

image.png

100% に再度移動

image.png

DataGridView の中のチェックボックスの大きさがちょっとうまく行ってないなぁ。

.NET Framework 4.8 だとどうなる?

.NET Framework 4.8 で ListBox と DataGridView を試してみました。結果は…

100%
image.png

150%
image.png

100%
image.png

ListBox の変な動きはなおってましたが、DataGridView は特に内部のコントロールが大きくなるとかはなかったです。

まとめというか感想

このうまくいかない部分にきちんと対応しようと思ったらどうするのが正攻法なんだろう??
今回はとりあえず動きを見てみたので、次やるとしたら対処法の調査かな…

参考

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

3連休どう過ごしたかをC#で表現してみた(List、暗号化、匿名型)

C#でのListの使い方、MemoryStreamとCryptoStreamで暗号化する方法、匿名型の実装方法を調べていて、ネタだと作りやすいのもあって、一応は勉強目的で実装してみました。

C# 9.0から可能になったトップレベルステートメント(Mainでなくusingと同じ層に書く)の形式です。

3連休の繰り返しをListで、連休中の行動は文字列で表して暗号化処理をしています。Listの処理と暗号化処理はそれぞれ別々です。匿名型は一番最後のListをループ処理する箇所で使用しています。

暗号化は、MemoryStreamとCryptoStreamクラスを使うのがポイントです。最後の箇所で復号化処理をしています。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Linq;

//3連休
List<DateTime> holidays = new()
{
    new DateTime(2020, 11, 21),
    new DateTime(2020, 11, 22),
    new DateTime(2020, 11, 23)
};

byte[] key;
byte[] IV;

//連休中にやってたこと
var behavior = "Stay home, Study little and Eat too many.";

//行動内容は知られたくないのでAES(Rijndael)で暗号化
byte[] encrypted;
using (Rijndael rijAlg = Rijndael.Create())
{
    //秘密鍵と暗号化ベクターを取得
    key = rijAlg.Key;
    IV = rijAlg.IV;

    ICryptoTransform encryptor = rijAlg.CreateEncryptor(key, IV);

    using (MemoryStream memory = new MemoryStream())
    {
        using (CryptoStream crypt = new CryptoStream(memory, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter writer = new StreamWriter(crypt))
            {
                writer.Write(behavior);
            }
            encrypted = memory.ToArray();
        }
    }
}

//Listの内容を出力 → 匿名型を使用(日付(〜日目)と出力する)
foreach (var today in holidays
    .Select((holiday, index) => new { Day = holiday, Index = index }))
{
    Console.WriteLine($"{today.Day.ToString("MM/dd")}({today.Index}日目): {BitConverter.ToString(encrypted)}");
}

//復号化処理
var decryptoText = string.Empty;
using (Rijndael rijAlg = Rijndael.Create())
{
    //暗号化で使用した秘密鍵・初期化ベクターを指定
    ICryptoTransform decryptor = rijAlg.CreateDecryptor(key, IV);

    using (MemoryStream memory = new MemoryStream(encrypted))
    {
        using (CryptoStream decrypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
        {
            using (StreamReader reader = new StreamReader(decrypto))
            {
                decryptoText = reader.ReadToEnd();
                Console.WriteLine($"Decrypted Text: {decryptoText}");
            }
        }
    }
}

一応、勉強のメモでもあるので残しました。

早く、コロナが収まってほしいですね…。

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