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

ANTLR4をC#から使ってみる #07 Visitorのスタックトレースを出してみる

ANTLR4をC#から使ってみる #05 Visitorで計算機を作るための試行錯誤で出したスタックトレースの図が良かったので、Listenerでも出した。

プログラムは、ANTLR4をC#から使ってみる #03 Listenerでの計算機作成のための試行錯誤の試しに色々出してみるのもの。

結果
2.png

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

C# Visual Studio 2019 .NET Core 3.1 クラスライブラリプロジェクトでWindows依存機能を使う

Visual Studio 2019の.NET Core 3.1 クラスライブラリプロジェクトは標準ではWindows依存の機能を使用できませんが、プロジェクト ファイルを編集することでWindows依存の機能を使用できます。

手順

  1. ソリューションエクスプローラーでクラスライブラリプロジェクトの名前を右クリックしてメニューを表示する。
  2. 「プロジェクト ファイルの編集」をクリックする。
  3. 内容を次の通り書き換える。具体的にはProjectのSdkを「Microsoft.NET.Sdk.WindowsDesktop」へ変更して、<UseWindowsForms>true</UseWindowsForms>を追加しています。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
      <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

</Project>
  • プロジェクトの設定を変更している場合はその他の要素が表示されますが、変更する部分はProjectのSdkと<UseWindowsForms>true</UseWindowsForms>の追加です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# 構造体・クラスのデバッグ表示をDebuggerDisplay属性で制御する

構造体・クラスは変数ウィンドウで{ConsoleApp1.TestClass}のように表示されますが、DebuggerDisplay属性やDebuggerTypeProxy属性により表示を変更できます。

ここではDebuggerDisplay属性のよく使うパターンを紹介します。以下、iint型、ffloatまたはdouble型の変数またはプロパティです。

表記 概要
[DebuggerDisplay("{X}")] Xの値を二重引用符ありで表示する。
[DebuggerDisplay("{X,nq}")] Xの値を二重引用符なしで表示する。
[DebuggerDisplay("X:{X}")] 「X:」に続けてXの値を二重引用符ありで表示する。
[DebuggerDisplay("X:{X,nq}")] 「X:」に続けてXの値を二重引用符なしで表示する。
[DebuggerDisplay("{i.ToString(\"D10\")}")] iの値を10桁以上になるまで0で埋めて二重引用符ありで表示する。
[DebuggerDisplay("{X.ToString(\"D10\"),nq}")] iの値を10桁以上になるまで0で埋めて二重引用符なしで表示する。
[DebuggerDisplay("{X.ToString(\"X10\",nq)}]") iの大文字16進数表現を10桁以上になるまで0で埋めて二重引用符なしで表示する。
[DebuggerDisplay("{f.ToString(\"F2\")}")] fの値を小数点以下2桁まで二重引用符ありで表示する。
[DebuggerDisplay("{f.ToString(\"F2\"),nq}]") fの値を小数点以下2桁まで二重引用符なしで表示する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity初心者Tips]FindとGetComponentを実行しエラーにどこから呼び出されたかを出す便利メソッド

なにをやるのか

Unityで、FindしたやつからGetComponentするという処理は、プログラムの設計によってシングルトンでやってないやつだとよくやります。それの便利版です。説明要らない方は最後へジャンプ。

GameObject gameObject = GameObject.Find("HOGEgo");
HogeComponent hogeComponent = gameObject.GetComponent<HogeComponent>();

親切にエラーを出してあげる

この時、それぞれでnullの可能性があるので、その場合にはエラーを吐いた方がうるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いので、以下のコードになります。

HogeComponent hogeComponent;
GameObject gameObject = GameObject.Find("HOGEgo");
if(gameObject == null){
  Debug.LogError("HOGEgo is not found");
}
else{
  hogeComponent = gameObject.GetComponent<HogeComponent>();
  if(gameObject == null){
    Debug.LogError("HogeComponent is not found");
  }
}

共用できるclassのメソッドにする

折角なので、適当なclassで何処でも使えるようにジェネリック化したstaticのメソッドにしましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CommonTools
{
    public class Tools : MonoBehaviour
    {
        public static T GetComponentInObject<T>(string objectName)
        {
            T c = default(T);
            GameObject gameObject = GameObject.Find(objectName);
            if (gameObject == null) {
                Debug.LogError(objectName + " is not found");
            }
            else {
                c = gameObject.GetComponent<T>();
                if (hogeComponent == null) {
                    Debug.LogError( nameof(T)+ " is not found");
                }
            }
            return c;
        }
    }
}

まあまあ良い感じですね!ここまではありがちです。

どこでやったか判るようにする

上のままでも問題ないんですけど、色々なところから呼び出されるのでエラーを見ても呼び出し元が判らないと修正に困るという問題があります。
うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いので、ここで呼び出し元が判るようにします。それには以下の仕組みを使います。

呼び出し元はstackから辿れる

アセンブラの知識がある方はご存知の通り、メソッドの呼び出し毎に戻りアドレスがpushされてstackに格納されて積まれます。そこを辿れば、どこから呼び出されているのか判るわけです。
参考:StackFrame クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.diagnostics.stackframe?view=netcore-3.1

リフレクションで名前を取得する

C#のリフレクションを利用すると、呼び出し元の名前が取得できます。
参考:MemberInfo.ReflectedType プロパティ
https://docs.microsoft.com/ja-jp/dotnet/api/system.reflection.memberinfo.reflectedtype?view=netcore-3.1

エラーで呼び出し元を出す完成版

上記を組み合わせてエラーのためのメソッドをFindGetComponentで共有化した完成例がこちらです。
https://gist.github.com/JunShimura/866cfe8736e4b40f35bf50113748cdd0

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CommonTools
{
    public class Tools : MonoBehaviour
    {
        public static T GetComponentInObject<T>(string objectName)
        {
            T c = default(T);
            GameObject go = GameObject.Find(objectName);
            if (go == null) {
                LogError(objectName);
            }
            else {
                c = go.GetComponent<T>();
                if (c == null) {
                    LogError(objectName + "." + nameof(T));
                }
            }
            return c;
        }
        static private void LogError(string s)
        {
            // StackFrameクラスでstackを2階層戻る
            System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(2);
            string methodName = objStackFrame.GetMethod().Name;
            Debug.LogError(s + " is not found at "
                + objStackFrame.GetMethod().ReflectedType.FullName
                + "." + objStackFrame.GetMethod().Name);
        }

    }

}

こうすると、

class GameDirector{
    void Start()
    {
        // hpゲージを取得
        hpGaugeImage = Tools.GetComponentInObject<Image>("hogeGauge");

このようなエラーになります。
image.png

うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いですね!

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

ドローン(Tello)を自作アプリで操縦する 1

最近ドローンを買いました。
DJI Mavic miniTelloです。
なぜ2つ買ったかというと、性能的にはMavic miniが上ですが、Telloはこんな感じで自作プログラムで飛ばすことができます。面白そう。

ただ、公式で用意されているEDUはブロックプログラミングみたい。
これはこれで教育的には非常にいいものかもしれませんが、個人的には何ならCで組み込みドローン開発したい勢いなので、もっとガンガンやりたい気分でした。

そこで、いい感じのを見つけました。

TELLO SDK

https://terra-1-g.djicdn.com/2d4dce68897a46b19fc717f3576b7c6a/Tello%20%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/For%20Tello/Tello%20SDK%20Documentation%20EN_1.3_1122.pdf

SDKと言っているのでライブラリとかかなあと思ったら、素のUDP通信リファレンスでした。(Pythonライブラリはあるみたいですが)
TELLOがルーター&アクセスポイントになって、UDPでコマンド送ると色々できるみたいです。
ということで、中身を見ていきます。

'2. ARCHITECTURE
Use Wi-Fi to establish communication between Tello and PC, Mac or Mobile device

Send Command & Receive Response
Tello IP: 192.168.10.1 UDP PORT:8889 <<- ->> PC/Mac/Mobile
Remark1: Set up a UDP client on PC, Mac or Mobile device to send and receive message
from Tello via the same port.
Remark2: Send “command” command to Tello via UDP PORT 8889 to initiate Tello’s SDK
mode, before sending all other commands.

Receive Tello State
Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT:8890
Remark3: Set up a UDP server on PC, Mac or Mobile device and listen the message from
IP 0.0.0.0 via UDP PORT 8890. Do Remark2 to start receiving state data if you haven’t.

Receive Tello Video Stream
Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT:11111
Remark4: Set up a UDP server on PC, Mac or Mobile device and listen the message from
IP 0.0.0.0 via UDP PORT 11111.
Remark5: Do Remark2 if you haven’t. Then send “streamon” command to Tello via UDP
PORT 8889 to start the streaming.

UDPの経路は3通りで、ポート番号で役割が分かれています。

  • コマンド送ってレスポンスを受ける8889番ポート
  • Telloのステータスが常に送られてくる11111番ポート
  • 映像が送られてくる11111ポート

これだけあれば映像&センサー情報を受けながらある程度自律運転できそう。

とりあえずコマンド送ってみる

C#でUDPを送ってみます。

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            UdpClient udpClient = new UdpClient(50000);
            udpClient.Connect("192.168.10.1", 8889);

            while (true)
            {
                Byte[] sendBytes = Encoding.ASCII.GetBytes(Console.ReadLine());
                udpClient.Send(sendBytes, sendBytes.Length);
                IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
                Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint);
                string returnData = Encoding.ASCII.GetString(receiveBytes);
                Console.WriteLine(returnData.ToString());
            }
        }
    }
}

適当。

command

"command"文字列を送ると"ok"が返ってきました。これでSDK modeになるみたいです。

takeoff

"takeoff"を送ると、離陸しました。

前後左右移動

"forward","back","right","left"で高度と向きを保ったまま前後左右移動できます。
例えば"forward 100"で100cm前進します。前進動作が終わったら"ok"が返ってくるので、その間は何も送らないほうがいいみたいです。

上下移動

"up","down"で高度が変わります。
"up 50"で上方向に50cm上昇しました。

これから

基本、Tello側は機体制御に集中して、色々操縦はPCとかスマホ側でやる仕組みみたいです。
なので、これから2つくらいやってみます。

  • M5Stack or M5Stick Cでコントローラ作る
    • IMUで直感的に操作できそう
  • 映像から顔認識して顔を追う
    • OpenCVとかでできるかなぁと
    • まぁ、それっぽいのやってみます

空き時間にちょろちょろとやっていきますー

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

C# - お手軽Windowsアイコン作成ツールをつくってみた

文字と図形(とりあえず4種類)からなるアイコンをサクッと作れるツールを作ってみた。

キャプチャ

image.png

アイコンの保存は「エクスポート」から。
設定項目は「テンプレート」から保存/読みだしできます。

注意

  • フォントには著作権があるので、作成したアイコンファイルを公開したりすることは問題があるかもしれません。
  • アイコンのキャッシュのリフレッシュ処理が環境依存な都合で、Windows10向けになっています。 Windows7の場合は、ソースコード内のICON_CACHE_CLEAR_EXE_OPTIONの値を"-ClearIconCache"に変更すればOK。

コンパイル方法

csc /unsafe IconMaker.cs IconUtility.cs

ソースコード - IconMaker.cs

IconMaker.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using IconUtility;


[Serializable]
public class SettingsClass
{
    public byte fg1Red;
    public byte fg1Green;
    public byte fg1Blue;
    public byte fg2Red;
    public byte fg2Green;
    public byte fg2Blue;
    public byte bg1Red;
    public byte bg1Green;
    public byte bg1Blue;
    public byte bg2Red;
    public byte bg2Green;
    public byte bg2Blue;
    public bool gradiation;
    public string fontName;
    public string textLine1;
    public string textLine2;
    public Decimal fontSizeLine1;
    public Decimal fontSizeLine2;
    public int yOffsetLine1;
    public int yOffsetLine2;
    public string bgFigureTypeName;
    public int iconSize;
}


class IconMaker:Form
{
    PictureBox pct;
    //int iconSize = 64;
    static readonly int DEFAULT_FONT_SIZE = 14;
    static readonly int CANVAS_SIZE = 256;
    static readonly string ICON_CACHE_CLEAR_EXE_NAME = "ie4uinit.exe";
    static readonly string ICON_CACHE_CLEAR_EXE_OPTION = "-show"; // windows10
    //Windows 10では「ie4uinit.exe -show」、
    //それ以外のバージョンでは「ie4uinit.exe -ClearIconCache」
//    const int ZOOM = 2;

    NumericUpDown nudSizeOfIcon;
    ComboBox cmbBgFigureType;

    TextBox txtLine1;
    TextBox txtLine2;
    NumericUpDown nudFontSizeOfLine1;
    NumericUpDown nudFontSizeOfLine2;
    NumericUpDown nudOffsetYLine1;
    NumericUpDown nudOffsetYLine2;

    TextBox txtFontName;

    Button btnColorForeground1;
    Button btnColorForeground2;
    Button btnColorBackground1;
    Button btnColorBackground2;
    CheckBox chkUseGradation;

    Bitmap bmpToBeSaved;
    Font curFontSizeIndependent;
    int curFontStyle;

    Color colorForeground1;
    Color colorForeground2;
    Color colorBackground1;
    Color colorBackground2;

    Pen curPen;
    Font curFont;
    Brush curBrush; // RedrawPreview呼びたびに更新
    PointF curPoint;
    GraphicsPath curPath;
    Graphics curG;

    bool resumeRedraw;


    IconMaker()
    {
        Text = "Icon Maker";
        resumeRedraw = false;

        colorForeground1 = Color.Blue;
        colorForeground2 = Color.Black;
        colorBackground1 = Color.Red;
        colorBackground2 = Color.Yellow;
        curFontSizeIndependent = new Font("メイリオ", (float)DEFAULT_FONT_SIZE);
        curFontStyle = (int)FontStyle.Regular; // Bold , Italic , Strikeout , Underline の bitOR を設定可能


        var menuStrip1 = new MenuStrip(); // https://dobon.net/vb/dotnet/control/menustrip.html

        SuspendLayout();
        menuStrip1.SuspendLayout();

        var templateMenuItem = new ToolStripMenuItem(){ Text = "テンプレート(&T)"};
        var iconMenuItem = new ToolStripMenuItem(){ Text = "エクスポート(&X)"};
        menuStrip1.Items.Add(templateMenuItem);
        menuStrip1.Items.Add(iconMenuItem);

        templateMenuItem.DropDownItems.Add( new ToolStripMenuItem("開く(&O)...", null, (s,e)=>{OpenTemplateWithDialog();}, Keys.Control | Keys.O) );
        templateMenuItem.DropDownItems.Add( new ToolStripMenuItem("保存(&S)...", null, (s,e)=>{SaveTemplateWithDialog();}, Keys.Control | Keys.S) );

        iconMenuItem.DropDownItems.Add( new ToolStripMenuItem("アイコン(.ico)として保存(&I)...", null, (s,e)=>{SaveImageWithDialog("ico");}, Keys.Control | Keys.I) );
        iconMenuItem.DropDownItems.Add( new ToolStripMenuItem("画像(.png)として保存(&P)...",     null, (s,e)=>{SaveImageWithDialog("png");}, Keys.Control | Keys.P) );

        Controls.AddRange(new Control[]{
            new Label(){                         Location = new Point( 10, 35),  Size = new Size( 80, 20), Text="アイコンサイズ"},
            nudSizeOfIcon = new NumericUpDown(){ Location = new Point(100, 35),  Size = new Size( 60, 20), Maximum = 256, Value = 64, Minimum = 16, Increment = 4}
        });
        nudSizeOfIcon.ValueChanged += (s,e)=>{
            int iconSize = (int)nudSizeOfIcon.Value;
            pct.Size = new Size(iconSize,iconSize);
            RedrawPreview();
        };


        GroupBox grpText = new GroupBox(){
            Location = new Point( 10, 60), Size = new Size(280,95), Text = "テキスト" 
        };
        Controls.Add(grpText);

        grpText.Controls.AddRange(new Control[]{
            new Label(){                              Location = new Point(140, 20), Size = new Size(60, 20), Text = "サイズ"},
            new Label(){                              Location = new Point(210, 20), Size = new Size(65, 20), Text = "位置調整"},
            new Label(){                              Location = new Point( 10, 40), Size = new Size(50, 20), Text = "1行目"},
            txtLine1           = new TextBox(){       Location = new Point( 60, 40), Size = new Size(70, 20), Text = "icon"  },
            nudFontSizeOfLine1 = new NumericUpDown(){ Location = new Point(140, 40), Size = new Size(60, 20), Maximum = 100, Value = 30, Minimum = 1, DecimalPlaces=1 },
            nudOffsetYLine1    = new NumericUpDown(){ Location = new Point(210, 40), Size = new Size(60, 20), Maximum = 256, Value = 0,  Minimum = -256 },
            new Label(){                              Location = new Point( 10, 65), Size = new Size(50, 20), Text = "2行目"},
            txtLine2           = new TextBox(){       Location = new Point( 60, 65), Size = new Size(70, 20), Text = "maker" },
            nudFontSizeOfLine2 = new NumericUpDown(){ Location = new Point(140, 65), Size = new Size(60, 20), Maximum = 100, Value = 15, Minimum = 1, DecimalPlaces=1 },
            nudOffsetYLine2    = new NumericUpDown(){ Location = new Point(210, 65), Size = new Size(60, 20), Maximum = 256, Value = 18, Minimum = -256 }
        });
        txtLine1.TextChanged += (s,e)=>{RedrawPreview();};
        txtLine2.TextChanged += (s,e)=>{RedrawPreview();};
        nudFontSizeOfLine1.ValueChanged += (s,e)=>{RedrawPreview();};
        nudFontSizeOfLine2.ValueChanged += (s,e)=>{RedrawPreview();};
        nudOffsetYLine1.ValueChanged += (s,e)=>{RedrawPreview();};
        nudOffsetYLine2.ValueChanged += (s,e)=>{RedrawPreview();};



        GroupBox grpFont = new GroupBox(){
            Location = new Point( 10,165), Size = new Size(280,50), Text = "フォント" 
        };
        Controls.Add(grpFont);

        Button btnSelectFont;
        grpFont.Controls.AddRange(new Control[]{
            txtFontName   = new TextBox(){ Location = new Point( 10, 20), Size = new Size(170,25), Text = curFontSizeIndependent.Name, ReadOnly = true },
            btnSelectFont = new Button(){  Location = new Point(185, 15), Size = new Size( 70,25),  Text = "変更..." }
        });
        btnSelectFont.Click += (s,e)=>{SelectAndUpdateFontWithDialog();};


        GroupBox grpColor = new GroupBox(){
            Location = new Point( 10,225), Size = new Size(280,100), Text = "色" 
        };
        Controls.Add(grpColor);

        grpColor.Controls.AddRange(new Control[]{
            new Label(){                         Location = new Point( 10, 30), Size = new Size(40,20), Text = "文字"},
            btnColorForeground1 = new Button(){  Location = new Point( 50, 20), Size = new Size(30,20), FlatStyle = FlatStyle.Flat},
            btnColorForeground2 = new Button(){  Location = new Point( 70, 30), Size = new Size(30,20), FlatStyle = FlatStyle.Flat},
            new Label(){                         Location = new Point( 10, 70), Size = new Size(40,20), Text = "背景"},
            btnColorBackground1 = new Button(){  Location = new Point( 50, 60), Size = new Size(30,20), FlatStyle = FlatStyle.Flat},
            btnColorBackground2 = new Button(){  Location = new Point( 70, 70), Size = new Size(30,20), FlatStyle = FlatStyle.Flat},
            chkUseGradation     = new CheckBox(){Location = new Point(140, 40), Size = new Size(100,25), Text = "グラデーション", Checked = true}
        });

        SetFlatButtonColor(btnColorForeground1, colorForeground1);
        SetFlatButtonColor(btnColorForeground2, colorForeground2);
        SetFlatButtonColor(btnColorBackground1, colorBackground1);
        SetFlatButtonColor(btnColorBackground2, colorBackground2);
        btnColorForeground1.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorForeground1);};
        btnColorForeground2.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorForeground2);};
        btnColorBackground1.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorBackground1);};
        btnColorBackground2.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorBackground2);};
        chkUseGradation.Click += (s,e)=>{
            ReflectGradationCheckToControls();
            RedrawPreview();
        };
        ReflectGradationCheckToControls();

        Controls.Add(
            pct = new PictureBox(){
                Location = new Point(350, 20),
                Size = new Size(CANVAS_SIZE, CANVAS_SIZE),
                Image = new Bitmap(CANVAS_SIZE, CANVAS_SIZE),
            }
        );


        Controls.Add(
            cmbBgFigureType = new ComboBox(){ Location = new Point( 20, 350),  Size = new Size( 80, 20),   DropDownStyle = ComboBoxStyle.DropDownList }
        );
        cmbBgFigureType.Items.AddRange(new string[]{"なし", "六角形", "角丸", "丸", "四角"});
        cmbBgFigureType.SelectedIndex = 1;
        cmbBgFigureType.SelectedIndexChanged += (s,e)=>{RedrawPreview();};


        ClientSize = new Size(350+CANVAS_SIZE, 400);

        Load+=(sender,e)=>{RedrawPreview();};

        curPen = Pens.Black;
        curBrush = Brushes.White; // とりあえず初期化しておく

        Controls.Add(menuStrip1);
        MainMenuStrip = menuStrip1;
        menuStrip1.ResumeLayout(false);
        menuStrip1.PerformLayout();
        ResumeLayout(false);
        PerformLayout();
    }

    void SetFlatButtonColor(Button btn, Color c)
    {
        btn.BackColor = c;
        btn.FlatAppearance.MouseOverBackColor = c;
        btn.FlatAppearance.MouseDownBackColor = c;
    }

    void SelectAndUpdateFontWithDialog()
    {
        FontDialog fd = new FontDialog();

        fd.Font = curFontSizeIndependent;
        fd.MaxSize = DEFAULT_FONT_SIZE;
        fd.MinSize = DEFAULT_FONT_SIZE;
        fd.FontMustExist = true;
        fd.AllowVerticalFonts = true;
        //fd.ShowColor = true;
        fd.ShowEffects = true;
        fd.FixedPitchOnly = false;
        fd.AllowVectorFonts = true;

        //ダイアログを表示する
        if (fd.ShowDialog() != DialogResult.Cancel) {
            curFontSizeIndependent = fd.Font;
            txtFontName.Text = curFontSizeIndependent.Name;
            RedrawPreview();
        }
    }


    void SelectAndUpdateColorWithDialog(Button sender, ref Color c)
    {
        ColorDialog cd = new ColorDialog();

        cd.Color = c;
        cd.FullOpen = true;
        if (cd.ShowDialog() == DialogResult.OK) {
            c = cd.Color;
            SetFlatButtonColor(sender, c);
            RedrawPreview();
        }
    }

    void ReflectGradationCheckToControls()
    {
        bool tmp = chkUseGradation.Checked;
        btnColorForeground2.Visible = tmp;
        btnColorBackground2.Visible = tmp;
    }

    void moveto(float x, float y)
    {
        curPoint.X = x;
        curPoint.Y = y;
    }

    void rmoveto(float rx, float ry)
    {
        curPoint.X += rx;
        curPoint.Y += ry;
    }

    void lineto(float x, float y)
    {
        curPath.AddLine(curPoint.X, curPoint.Y, x, y);
        curPoint.X = x;
        curPoint.Y = y;
    }

    void rlineto(float rx, float ry)
    {
        curPath.AddLine(curPoint.X, curPoint.Y, curPoint.X+rx, curPoint.Y+ry);
        curPoint.X += rx;
        curPoint.Y += ry;
    }

    void charpath(string s)
    {
        var sf = new StringFormat();
        sf.Alignment     = StringAlignment.Center;    // 横方向の中央
        sf.LineAlignment = StringAlignment.Center;    // 縦方向の中央
        FontFamily ff = curFont.FontFamily;
        curPath.AddString(s, ff, curFontStyle, curFont.Size, curPoint, sf);
    }

    void newpath()
    {
        curPath.Reset();
        curPath.StartFigure();
    }

    void closepath()
    {
        curPath.CloseFigure();
    }

    void stroke()
    {
        if ( curG != null ) {
            curG.DrawPath(curPen, curPath);
        }
    }
    void fill()
    {
        if ( curG != null ) {
            curPath.FillMode = FillMode.Winding;
            curG.FillPath(curBrush, curPath);
        }
    }
    /*
    void eofill()
    {
        if ( curG != null ) {
            curPath.FillMode = FillMode.Alternate;
            curG.FillPath(curBrush, curPath);
        }
    }
    */

    float cos(float degree)
    {
        return (float)Math.Cos((degree/180.0)*Math.PI);
    }
    float sin(float degree)
    {
        return (float)Math.Sin((degree/180.0)*Math.PI);
    }


    void RedrawPreview()
    {
        if(resumeRedraw){return;}

        int iconSize = (int)nudSizeOfIcon.Value;
        bmpToBeSaved = CreateTransparentBitmap(iconSize, iconSize);
        curPath = new GraphicsPath();
        curG = Graphics.FromImage(bmpToBeSaved);
        curG.SmoothingMode = SmoothingMode.AntiAlias;

        try {
            if (chkUseGradation.Checked) {
                curBrush = new LinearGradientBrush(new Point(0, 0), new Point(iconSize, iconSize), colorBackground1, colorBackground2);
            }
            else {
                curBrush = new SolidBrush(colorBackground1);
            }

            DrawFigure();

            if (chkUseGradation.Checked) {
                curBrush = new LinearGradientBrush(new Point(0, 0), new Point(iconSize, iconSize), colorForeground1, colorForeground2);
            }
            else {
                curBrush = new SolidBrush(colorForeground1);
            }

            curFont = new Font(curFontSizeIndependent.Name, (float)nudFontSizeOfLine1.Value);
            newpath();
            moveto(iconSize/2, iconSize/2 + (int)nudOffsetYLine1.Value);
            charpath(txtLine1.Text);
            closepath();
            fill();

            if ( txtLine2.Text != "" ) {
                curFont = new Font(curFontSizeIndependent.Name, (float)nudFontSizeOfLine2.Value);
                newpath();
                moveto(iconSize/2, iconSize/2 + (int)nudOffsetYLine2.Value);
                charpath(txtLine2.Text);
                closepath();
                fill();
            }
        }
        finally {
            curG.Dispose();
            curG = null;
        }

        int zoom = 1;// 2;
        Graphics g = Graphics.FromImage(pct.Image);
        g.FillRectangle(Brushes.White, 0, 0, bmpToBeSaved.Width*zoom, bmpToBeSaved.Height*zoom); // pct.Image.Width, pct.Image.Height);
        g.DrawImage(bmpToBeSaved, 0, 0, bmpToBeSaved.Width*zoom, bmpToBeSaved.Height*zoom);
        g.Dispose();

        pct.Refresh();
    }


    void DrawFigure()
    {
        int iconSize = (int)nudSizeOfIcon.Value;

        string figureType = cmbBgFigureType.Text;

        if ( figureType == "六角形" ) {
            newpath();
            for ( int deg=0 ; deg<360 ; deg+=60 ) {
                float x = iconSize/2 + (iconSize/2 - 1)*cos(deg);
                float y = iconSize/2 + (iconSize/2 - 1)*sin(deg);
                if ( deg == 0 ) { moveto(x,y); } else { lineto(x,y); }
            }
            closepath();
            fill();
        }
        else if ( figureType == "丸" ) {
            newpath();
            curPath.AddArc(0, 0, iconSize, iconSize, 0, 360);
            closepath();
            fill();
        }
        else if ( figureType == "四角" ) {
            newpath();
            curPath.AddRectangle(new Rectangle(0, 0, iconSize, iconSize));
            closepath();
            fill();
        }
        else if ( figureType == "角丸" ) {
            int cornerSize = iconSize/2;
            newpath();
            curPath.AddArc(0,                   0,                   cornerSize, cornerSize, 180, 90);
            curPath.AddArc(iconSize-cornerSize, 0,                   cornerSize, cornerSize, 270, 90);
            curPath.AddArc(iconSize-cornerSize, iconSize-cornerSize, cornerSize, cornerSize,   0, 90);
            curPath.AddArc(0,                   iconSize-cornerSize, cornerSize, cornerSize,  90, 90);
            closepath();
            fill();
        }
    }


    bool OpenTemplateWithDialog()
    {
        OpenFileDialog ofd = new OpenFileDialog();
        ofd.FileName = "テンプレート.xml";
        ofd.Filter = "XMLファイル(*.xml)|*.xml";
        // ofd.Filter = "XMLファイル(*.xml)|*.xml|すべてのファイル(*.*)|*.*";
        // ofd.FilterIndex = 1;
        // ofd.Title = "開くファイルを選択してください";
        ofd.RestoreDirectory = true;
        // ofd.CheckFileExists = true;
        // ofd.CheckPathExists = true;

        if (ofd.ShowDialog() == DialogResult.OK) {
            return LoadSettingsFromXml(ofd.FileName);
        }
        else {
            return false;
        }
    }

    bool SaveTemplateWithDialog()
    {
        SaveFileDialog sfd = new SaveFileDialog();
        sfd.FileName = "新しいIconMakerテンプレート.xml";
        // sfd.InitialDirectory = @"C:\";
        sfd.Filter = "XMLファイル(*.xml)|*.xml";
        sfd.Title = "保存先のファイルを選択してください";
        sfd.RestoreDirectory = true;
        // sfd.OverwritePrompt = true;
        // sfd.CheckPathExists = true;

        //ダイアログを表示する
        if (sfd.ShowDialog() == DialogResult.OK) {
            return SaveSettingsToXml(sfd.FileName);
        }
        else {
            return false;
        }
    }

    bool SaveImageWithDialog(string fmt)
    {
        SaveFileDialog sfd = new SaveFileDialog();
        // sfd.InitialDirectory = @"C:\";
        if ( fmt == "ico" ) {
            sfd.FileName = "新しいアイコン.ico";
            sfd.Filter = "アイコンファイル(*.ico)|*.ico";
        }
        else if ( fmt == "png" ) {
            sfd.FileName = "新しい画像.png";
            sfd.Filter = "画像ファイル(*.png)|*.png";
        }
        else {
            return false;
        }
        sfd.Title = "保存先のファイルを選択してください";
        sfd.RestoreDirectory = true;
        // sfd.OverwritePrompt = true;
        // sfd.CheckPathExists = true;

        //ダイアログを表示する
        if (sfd.ShowDialog() == DialogResult.OK) {
            bool saveResult = false;
            if ( fmt == "ico" ) {
                saveResult = SaveAsIcon(sfd.FileName);
                if ( saveResult ) {
                    RunClearCache();
                }
            }
            else if ( fmt == "png" ) {
                saveResult = SaveAsPng(sfd.FileName);
            }
            return saveResult;
        }
        else {
            return false;
        }
    }

    bool SaveAsIcon(string destPath)
    {
        Icons icons;
        icons = new Icons();
        icons.AddIcon(bmpToBeSaved);
        try {
            icons.SaveToFile(destPath);
            return true;
        }
        catch (IOException e) {
            MessageBox.Show(e.ToString());
        }
        return false;
    }

    bool SaveAsPng(string destPath)
    {
        try {
            bmpToBeSaved.Save(destPath, System.Drawing.Imaging.ImageFormat.Png);
            return true;
        }
        catch (IOException e) {
            MessageBox.Show(e.ToString());
        }
        return false;
    }

    void RunClearCache()
    {
        try {
            System.Diagnostics.Process.Start(ICON_CACHE_CLEAR_EXE_NAME, ICON_CACHE_CLEAR_EXE_OPTION);
        }
        catch (System.ComponentModel.Win32Exception e) {
            Console.WriteLine(e);
        }
    }

    bool SaveSettingsToXml(string destPath)
    {
        var t = new SettingsClass()
        {
            fg1Red   = colorForeground1.R,
            fg1Green = colorForeground1.G,
            fg1Blue  = colorForeground1.B,
            fg2Red   = colorForeground2.R,
            fg2Green = colorForeground2.G,
            fg2Blue  = colorForeground2.B,
            bg1Red   = colorBackground1.R,
            bg1Green = colorBackground1.G,
            bg1Blue  = colorBackground1.B,
            bg2Red   = colorBackground2.R,
            bg2Green = colorBackground2.G,
            bg2Blue  = colorBackground2.B,
            gradiation = chkUseGradation.Checked,
            fontName   = curFontSizeIndependent.Name,
            fontSizeLine1 = nudFontSizeOfLine1.Value,
            fontSizeLine2 = nudFontSizeOfLine2.Value,
            yOffsetLine1 = (int)nudOffsetYLine1.Value,
            yOffsetLine2 = (int)nudOffsetYLine2.Value,
            textLine1  = txtLine1.Text,
            textLine2  = txtLine2.Text,
            iconSize =  (int)nudSizeOfIcon.Value,
            bgFigureTypeName = cmbBgFigureType.Text,
        };

        var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SettingsClass));
        try {
            var sw = new System.IO.StreamWriter(destPath, false, new System.Text.UTF8Encoding(false));
            try {
                serializer.Serialize(sw, t);
            }
            finally {
                sw.Close();
            }
            return true;
        }
        catch( IOException e ) {
            MessageBox.Show(e.ToString());
            return false;
        }
    }


    bool LoadSettingsFromXml(string srcPath)
    {
        // https://www.atmarkit.co.jp/ait/articles/1704/19/news021.html

        var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(SettingsClass));
        SettingsClass t;
        //var xmlSettings = new System.Xml.XmlReaderSettings();

        try {
            using (var streamReader = new StreamReader(srcPath, System.Text.Encoding.UTF8))
            using (var xmlReader = System.Xml.XmlReader.Create(streamReader)) //, xmlSettings))
            {
                t = (SettingsClass)xmlSerializer.Deserialize(xmlReader);
            }
        }
        catch (IOException e) {
            MessageBox.Show(e.ToString());
            return false;
        }
        catch (InvalidOperationException e) {
            MessageBox.Show(e.ToString());
            return false;
        }

        resumeRedraw = true;
        try {
            colorForeground1 = Color.FromArgb(t.fg1Red, t.fg1Green, t.fg1Blue);
            colorForeground2 = Color.FromArgb(t.fg2Red, t.fg2Green, t.fg2Blue);
            colorBackground1 = Color.FromArgb(t.bg1Red, t.bg1Green, t.bg1Blue);
            colorBackground2 = Color.FromArgb(t.bg2Red, t.bg2Green, t.bg2Blue);
            SetFlatButtonColor(btnColorForeground1, colorForeground1);
            SetFlatButtonColor(btnColorForeground2, colorForeground2);
            SetFlatButtonColor(btnColorBackground1, colorBackground1);
            SetFlatButtonColor(btnColorBackground2, colorBackground2);

            chkUseGradation.Checked = t.gradiation;
            ReflectGradationCheckToControls();

            txtLine1.Text = t.textLine1;
            txtLine2.Text = t.textLine2;
            nudFontSizeOfLine1.Value = t.fontSizeLine1;
            nudFontSizeOfLine2.Value = t.fontSizeLine2;
            nudOffsetYLine1.Value = t.yOffsetLine1;
            nudOffsetYLine2.Value = t.yOffsetLine2;
            cmbBgFigureType.Text = t.bgFigureTypeName;
            nudSizeOfIcon.Value = t.iconSize;

            curFontSizeIndependent = new Font(t.fontName, (float)DEFAULT_FONT_SIZE);
            txtFontName.Text = curFontSizeIndependent.Name;
        }
        finally {
            resumeRedraw = false;
        }
        RedrawPreview();
        return true;
    }


    // 透過色で初期化
    Bitmap CreateTransparentBitmap(int width, int height)
    {
        Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        BitmapData bd = bmp.LockBits(new Rectangle(0,0,width,height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

        try {
            unsafe {
                // 書き込み
                byte* ptr = (byte*)bd.Scan0;
                for ( int y=0 ; y<height ; y++ ) {
                    for ( int x=0 ; x<width ; x++ ) {
                        ptr[y*bd.Stride + 4*x    ] = 0;// B
                        ptr[y*bd.Stride + 4*x + 1] = 0;// G
                        ptr[y*bd.Stride + 4*x + 2] = 0;// R
                        ptr[y*bd.Stride + 4*x + 3] = 0;// alpha = 0 (透過)
                    }
                }
            }
        }
        finally {
            bmp.UnlockBits(bd);
        }

        return bmp;
    }


    [STAThread]
    static void Main()
    {
        Application.Run(new IconMaker());
    }
}

ソースコード - IconUtility.cs

IconUtility.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;


namespace IconUtility
{
    public class Icons
    {
        class IconEntry
        {
            [StructLayout(LayoutKind.Sequential)]
            public struct IconDir
            {
                public short  icoReserved; // must be 0
                public short  icoResourceType; //must be 1 for icon
                public short  icoResourceCount;

                public IconDir(int n)
                {
                    icoReserved = 0;
                    icoResourceType = 1;
                    icoResourceCount = (short)n;
                }
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct IconDirEntry
            {
                byte   _Width;
                byte   _Height;
                public byte   ColorCount;
                public byte   reserved1;
                public short  reserved2;
                public short  reserved3;
                public int    icoDIBSize;
                public int    icoDIBOffset;

                public int Width{get{return (_Width>0)?_Width:256;}}
                public int Height{get{return (_Height>0)?_Height:256;}}

                public IconDirEntry(int w, int h)
                {
                    if ( w<0 || w>256 || h<0 || h>256 ) {
                        throw new Exception("Size parameter error");
                    }
                    _Width  = (byte)w;
                    _Height = (byte)h;
                    ColorCount=0;
                    reserved1=0;
                    reserved2=0;
                    reserved3=0;
                    icoDIBSize=0;
                    icoDIBOffset=0;
                }
            }

            [StructLayout(LayoutKind.Sequential)]
            struct BitmapInfoHeader
            {
                public int    biSize; // must be 40
                public int    biWidth;
                public int    biHeight;
                public short  biPlanes; // must be 1
                public short  biBitCount; // color
                public int    biCompression; // 0:not compress
                public int    biSizeImage;
                public int    biXPixPerMeter;
                public int    biYPixPerMeter;
                public int    biClrUsed;
                public int    biClrImportant;

                public BitmapInfoHeader(int w, int h)
                {
                    biSize = 40;
                    biWidth = w;
                    biHeight = h*2; // 本体とmaskを含むため2倍とする決まりらしい
                    biPlanes = 1;
                    biBitCount = 32;
                    biCompression=0;
                    biSizeImage=0;
                    biXPixPerMeter=0;
                    biYPixPerMeter=0;
                    biClrUsed=0;
                    biClrImportant=0;
                }
            }

            IconDirEntry     iconDirEntry;
            BitmapInfoHeader bitmapInfoHeader;
            byte[] bitmapBody;
            byte[] bitmapMask;

            public System.Drawing.Size Size{get{return new System.Drawing.Size(iconDirEntry.Width, iconDirEntry.Height);}}
            public int Width{get{return iconDirEntry.Width;}}
            public int Height{get{return iconDirEntry.Height;}}
            int BitPerPixel{get{return bitmapInfoHeader.biBitCount;}}

            int CalcDIBSize()
            {
                return Marshal.SizeOf(typeof(BitmapInfoHeader)) + bitmapBody.Length + bitmapMask.Length;
            }

            public int UpdateIconDirEntry(int icoDIBOffset)
            {
                iconDirEntry.icoDIBOffset = icoDIBOffset;
                iconDirEntry.icoDIBSize   = CalcDIBSize();
                return iconDirEntry.icoDIBSize;
            }

            public void WriteIconDirEntryTo(BinaryWriter writer)
            {
                Icons.CopyDataToByteArray<IconDirEntry>(writer, iconDirEntry);
            }

            public void WriteDataTo(BinaryWriter writer)
            {
                Icons.CopyDataToByteArray<BitmapInfoHeader>(writer, bitmapInfoHeader);
                writer.Write(bitmapBody);
                writer.Write(bitmapMask);
            }

            IconEntry(IconDirEntry _iconDirEntry, BitmapInfoHeader _bitmapInfoHeader, byte[] _bitmapBody, byte[] _bitmapMask)
            {
                iconDirEntry = _iconDirEntry;
                bitmapInfoHeader = _bitmapInfoHeader;
                bitmapBody = _bitmapBody;
                bitmapMask = _bitmapMask;
            }

            // 本体
            public IconEntry(Bitmap bmp, Color? alphaColor)
            {
                int w = bmp.Width;
                int h = bmp.Height;

                if ( w>256 || h>256 ) {
                    throw new Exception("size parameter error");
                }

                iconDirEntry = new IconDirEntry(w, h);
                bitmapInfoHeader = new BitmapInfoHeader(w, h);
                bitmapBody = new byte[Icons.GetBitmapBodySize(w, h, bitmapInfoHeader.biBitCount)];
                bitmapMask = new byte[Icons.GetBitmapMaskSize(w, h)];

                Draw32bppBitmapToData(bmp, alphaColor);
            }

            void Draw32bppBitmapToData(Bitmap bmp, Color? alphaColor)
            {
                Array.Clear(bitmapMask, 0, bitmapMask.Length);
                Array.Clear(bitmapBody, 0, bitmapBody.Length);

                BitmapData bd = bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

                try {
                    IntPtr ptr = bd.Scan0;
                    byte[] pixels = new byte[bd.Stride * bmp.Height];
                    Marshal.Copy(ptr, pixels, 0, pixels.Length);

                    int maskStride = (((Width+7)/8+3)/4)*4;
                    int icoStride = Width*4;

                    for (int y = 0; y < bd.Height; y++) {
                        for (int x = 0; x < bd.Width; x++) {
                            int posIco = y * icoStride + 4*x;

                            int bytePosMask = y * maskStride + x/8;
                            int bitPosMask = 7-(x%8);

                            int pos = (bd.Height-1-y) * bd.Stride + x * 4;
                            bitmapBody[posIco  ] = pixels[pos];   //blue;
                            bitmapBody[posIco+1] = pixels[pos+1]; //green;
                            bitmapBody[posIco+2] = pixels[pos+2]; //red;
                            bitmapBody[posIco+3] = pixels[pos+3]; //alpha

                            if ( pixels[pos+3] == 0 ||
                            (alphaColor != null &&
                                pixels[pos]  ==alphaColor.Value.B &&
                                pixels[pos+1]==alphaColor.Value.G &&
                                pixels[pos+2]==alphaColor.Value.R  )) {
                                //bitmapMask[bytePosMask] |= (byte)(1<<bitPosMask);
                                // 32bit色のiconだとmaskではなくalpha channelが使用されるっぽい
                                bitmapBody[posIco+3] = 0x00;
                            }
                        }
                    }
                }
                finally {
                    bmp.UnlockBits(bd);
                }
            }
        }

        int UpdateIconDirEntries()
        {
            iconDir.icoResourceCount = (short)iconEntries.Count;

            int offset  =  Marshal.SizeOf(typeof(IconEntry.IconDir))  +  iconEntries.Count * Marshal.SizeOf(typeof(IconEntry.IconDirEntry));

            for (int i=0;i<iconEntries.Count;i++) {
                offset += iconEntries[i].UpdateIconDirEntry(offset);
            }
            return offset;
        }

        static int GetBitmapBodySize(int w, int h, int bitCount)
        {
            return ((((w*bitCount + 7)/8)+3)/4)*4 * h;
        }

        static int GetBitmapMaskSize(int w, int h)
        {
            return ((((w+7)/8)+3)/4)*4 * h;
        }

        static TStruct CopyDataToStruct<TStruct> (BinaryReader reader) where TStruct : struct
        {
            var size = Marshal.SizeOf(typeof(TStruct));
            var ptr = IntPtr.Zero;

            try {
                ptr = Marshal.AllocHGlobal(size);
                Marshal.Copy(reader.ReadBytes(size), 0, ptr, size);
                return (TStruct)Marshal.PtrToStructure(ptr, typeof(TStruct));
            }
            finally {
                if (ptr != IntPtr.Zero) {
                    Marshal.FreeHGlobal(ptr);
                }
            }
        }

        static void CopyDataToByteArray<TStruct>(BinaryWriter writer, TStruct s) where TStruct : struct
        {
            var size = Marshal.SizeOf(typeof(TStruct));
            var buffer = new byte[size];
            var ptr = IntPtr.Zero;

            try {
                ptr = Marshal.AllocHGlobal(size);
                Marshal.StructureToPtr(s, ptr, false);
                Marshal.Copy(ptr, buffer, 0, size);
            }
            finally {
                if (ptr != IntPtr.Zero) {
                    Marshal.FreeHGlobal(ptr);
                }
            }
            writer.Write(buffer);
        }



        IconEntry.IconDir iconDir;
        List<IconEntry> iconEntries;

        // ------------------------------------------------------------------------------
        // public members

        public int Count {get{return iconEntries.Count;}}

        public Icons()
        {
            iconDir      = new IconEntry.IconDir(0);
            iconEntries  = new List<IconEntry>();
        }

        public void AddIcon(Bitmap bmp, Color alphaColor)
        {
            iconEntries.Add(new IconEntry(bmp, alphaColor));
            UpdateIconDirEntries();
        }

        public void AddIcon(Bitmap bmp)
        {
            iconEntries.Add(new IconEntry(bmp, null));
            UpdateIconDirEntries();
        }

        public bool SaveToFile(string path)
        {
            //int size = 
            UpdateIconDirEntries();

            using ( var fs = new FileStream(path, FileMode.Create) ) {
                using ( var writer = new BinaryWriter(fs) ) {
                    CopyDataToByteArray<IconEntry.IconDir>(writer, iconDir);

                    foreach(var t in iconEntries) {
                        t.WriteIconDirEntryTo(writer);
                    }

                    foreach(var t in iconEntries) {
                        t.WriteDataTo(writer);
                    }
                }
            }

            return true;
        }
    }
}

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

C#触ってて便利と思った機能について

はじめに

最近になってUnityで開発することが増え、C#に触ることが多くなったので「使ってこれは便利だな」と感じたものをここに備忘録がてらまとめておきます。(主はC++からC#を触ってるのでC++と比較しての視点で書いてます。)
Unity2019.4.0f1時点でのバージョンはC#7.3が標準だそうです。

プロパティ構文

C++ではことあるごとにGetter/Setterを作らないといけなかったがC#ならこのように記述できる。

test.cs
public class BaseCompornent : MonoBehaviour {
    //座標
    public Vector3 Position
    {
        set { transform.position = value; }
        get { return transform.position; }
    }
    public float PosX
    {
        set
        {
            Vector3 pos = Position;
            pos.x = value;
            Position = pos;
        }
        get { return Position.x; }
    }

参照するときはこのようにメソッドを呼び出すことなくメンバ変数のように扱える。

test.cs
    public void Function()
    {
        PosX += 100;
        var posX = posX;
        PosX++;
    }

Unityでゲーム作ってるとどうしてもクラス間の値の受け渡しが多くなるため、この機能はとても助かる。
ちなみにget/setのどちらかは省略することが可能である。
また自動実装プロパティといって単純に「get; set;」記述することもできる。その際はプロパティ自体がデータを保有することになる。

名前付き引数

良く調べたらこの機能C++ではないそうです。(C#は4.0から実装されたみたい)
簡単に言えば指定した引数だけを渡せれるってやつです。
デフォルト引数と兼ねたコード例です。

test.cs
    public void Function()
    {
        AddPosition();
        AddPosition(15.0f, 25.0f);
        AddPosition(1.0f,20.0f, 30.0f);
        AddPosition(vy: 30.0f);     //指定することが可能
    }

    public void AddPosition(float vx = 0.0f, float vy = 0.0f, float vz = 0.0f)
    {
        PosX += vx;
        PosY += vy;
        PosZ += vz;
    }

何気にこの機能最近知ったので、上手いこと使いこなしていきたいですね。
VBには昔からある機能だそうです。

LINQ

最初、LINQとはなんぞやとなりましたが要はC#独自のSQLみたいなやつです。(正式名称は統合言語クエリと呼ぶみたい)
データベース関係に疎いので上手い使い方は出来ていないかもしれませんが、現在開発中のコードではこんな感じで使っています。

LINQ_Test.cs
    private void RemovePort(BaseNode node, Port port)
    {
        var targetEdge = edges.ToList().Where(x => x.output.portName == port.portName && x.output.node == port.node);

        if (targetEdge.Any())
        {
            var edge = targetEdge.First();
            edge.input.Disconnect(edge);
            RemoveElement(targetEdge.First());
        }

        //省略
    }

やってることとしては「where」の部分でエッジリストから「エッジの出力ポート名と対象のポートのポート名が同じ かつ 対象ノードと出力先のノードが同じ」という条件のエッジを抽出しています。
そして「Any()」の行で抽出したエッジがあれば if 文内の処理をするようにしています。
指定した条件の要素を抽出したいときに結構使う便利だなと感じます。

リフレクション

この機能もC++には現状ありません。(これぞとばかりにC++に恨みがあるんかって感じですけど)
何が出来るかというと、クラスの情報をデータとして扱えます。(メタデータ)Typeクラスを使って実装します。
コード例も開発のものから引っ張てきました。

BaseNode.cs
public class BaseNode : Node
{
    public string Guid;
    public Type type;
    public bool EntryPoint;

    public BaseNode()
    {
        Guid = System.Guid.NewGuid().ToString();
        type = this.GetType();
        title = type.Name.Replace("Node", "");  //クラス名から "Node" を削除したもの
        EntryPoint = false;
    }

まだこれは単純ですが、BaseNodeのメンバにTypeクラスの変数を宣言し、コンストラクタの「GetType()」でBaseNodeクラスをタイプオブジェクトとして格納しています。また文字列としても取得することが出来ます。「type.Name」でクラス名を取得し、"Node"部分を削除してそのままNodeのタイトルとして流用しています。
こうすることでこのBaseNodeクラスを継承したクラスでも機能が働き、子クラスのクラス名を勝手に取得して反映させてくれます。
Typeクラスからインスタンスを生成をすることもできます。詳しくは参考記事をご覧ください。

おわりに

Unityを触ってて便利だなと感じたC#の機能はこんな感じです。(あとの機能はC++にもある機能だったりので省いてます。)
まだ他にも「式木」とかあるんですけど、使いどころがあまりないと感じたので割愛。
もっとこういう使い方あるよとか便利な機能があるよっていうアドバイスがありましたら気軽にコメントしてくれるととても助かります。

参考記事

C#とC++の比較
Unityで使える!C#の便利な機能7選
オプション引数・名前付き引数
C#のプロパティについて調べてみた
はじめてのLINQ
C#リフレクションTIPS 55連発
リフレクションを利用して文字列からクラス操作

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