C#のTIPS集
作成日: 2004/3/27  最終更新日: 2005/1/18



◆概要

ここでは,日頃VisualStudioやC#でプログラミングを行う際に発見したちょっとしたテクニックや,ユーティリティ的な簡単なサンプルコードなどについて徒然にまとめていきます.時折更新していく予定です.






◆ディレクトリサイズの取得(ディレクトリの再帰的な探索)
2004/3/31

ページトップへ

ディレクトリの正確なサイズを取得するためには,ディレクトリ内の全サブディレクトリを再帰的に探索し,全ファイルのサイズを取得して合計する必要があり,少しだけ面倒です.ここでは,あるフォルダ以下のファイルサイズの合計を表示する簡単なコードを書いてみました.
ディレクトリを再帰的に探索するためには,Directory.GetDirectories()と,Directory.GetFiles()を利用します.
マイクロソフトのページの再帰的な検索のサンプルを参考にしています.

    using System;
    using System.IO;


    public class Util
    {
        //ファイルサイズの合計(バイト)
        static long fileSizeSum;

        //パブリックメソッド.
        //特定のディレクトリ以下の全ファイルサイズの合計を再帰的に取得.
        public static long GetDirectorySize(string directory)
        {
            
            try
            {
                fileSizeSum = 0;
                DirectoryInfo rootInfo = new DirectoryInfo(directory);
                foreach(FileInfo fileInfo in rootInfo.GetFiles())
                {
                    fileSizeSum += fileInfo.Length;
                }
            
                foreach(string d in Directory.GetDirectories(directory))
                {
                    DirectoryInfo info = new DirectoryInfo(d);
                    foreach(FileInfo fileInfo in info.GetFiles())
                    {
                        fileSizeSum += fileInfo.Length;
                    }
                    GetSubDirectorySize(d);
                }
            }
            catch(Exception ex)
            {
                //エラー処理
            }
            return fileSizeSum;
        }


        //プライベートメソッド.
        //ディレクトリの数だけ再帰的に呼び出される.
        private static void GetSubDirectorySize(string directory)
        {
            foreach(string d in Directory.GetDirectories(directory))
            {
                DirectoryInfo info = new DirectoryInfo(d);
                foreach(FileInfo fileInfo in info.GetFiles())
                {
                    fileSizeSum += fileInfo.Length;
                }
                //同一メソッドを再帰的に呼び出す.
                GetSubDirectorySize(d);
            }
            return;
        }
    }
このメソッドの使い方は以下のようになります.
     //使い方
    long directorySize = 0;
    string directory = @"C:\Windows\Temp";

    if(Directory.Exists(directory))
    {    
        //ディレクトリサイズを取得.
        directorySize = Util.GetDirectorySize(directory);
    }
    else
    {
        //エラー処理
    }	
        

ページトップへ




◆各種値型とバイト配列(byte [])の変換
2004/3/27

ページトップへ

基本的な値型とバイト配列(byte[])間でデータを変換するには,System.BitConverterクラスを利用します.

        using System;
        

        byte [] bytes = new byte [4]{0xff, 0xff, 0x00 ,0x00};
        int i;

        //バイト配列→int型変換
        i = BitConverter.ToInt32(bytes, 0);  // i = 65535
        i = 255;
        //int型→バイト配列変換
        bytes = BitConverter.GetBytes((int)i);    //bytes = {0xff, 0, 0, 0}
        
BitConverterクラスには,文字列型⇔バイト配列を変換するメソッドは含まれない模様です.
シリアル通信などで使う機会が多いので,簡単な変換メソッドを書いてみました.
まず, 文字列→バイト配列に変換するメソッドは次のようになります.
        //1文字辺りのバイトサイズ
        public enum ByteSize {One, Two};

       //文字列(string)をバイト配列(byte [])に変換
       //ASCII文字列処理する場合はByteSize.Oneを,
       //日本語などを含む文字列を処理する場合はByteSize.Twoを指定
         public byte[] ConvertStrToBytes(string str, ByteSize byteSize)
         {
            byte [] bytes;
            byte [][] dummyBytes;
            char [] dummyChars = str.ToCharArray(0,str.Length);
            //1文字=1バイトの場合
            if(byteSize == ByteSize.One)
            {
                bytes = new byte[str.Length];
                dummyBytes = new byte[str.Length][];
                for(int i=0;i<dummyChars.Length;i++)
                {
                    dummyBytes[i] = BitConverter.GetBytes(dummyChars[i]);
                    bytes[i] = dummyBytes[i][0];
                }
            }
            //1文字=2バイトの場合
            else
            {
                bytes = new byte[str.Length*2];
                dummyBytes = new byte[str.Length*2][];
                int allLength = 0;
                for(int i=0;i<dummyChars.Length;i++)
                {
                    dummyBytes[i] = BitConverter.GetBytes(dummyChars[i]);
                    for(int j = 0;j<dummyBytes[i].Length;j++) 
                    {
                        bytes[allLength + j] = dummyBytes[i][j]; 
                    }
                    allLength += 2; 
                }
            }
            return bytes;
        }


        //メソッド呼び出し側の処理
        string str = "ascii";            
        byte [] bytes;

        bytes = ConvertStrToBytes(str, ByteSize.One);    //bytes = {0x61, 0x73, 0x63, 0x69, 0x69}
		
        str = "日本語";
        bytes = ConvertStrToBytes(str, ByteSize.Two);    //bytes = {0xE5, 0x65, 0x2C, 0x67, 0x9E, 0x8A}
		
         
逆に,バイト配列→文字列を変換するメソッドは次のようになります.
       //バイト配列(byte [])を文字列(string)に変換
        //ASCII文字列処理する場合はByteSize.Oneを,
        // 日本語などを含む文字列を処理する場合はByteSize.Twoを指定
        public static string ConvertBytesToStr(byte [] bytes, ByteSize byteSize)
        {
            char [] dummyChars;
            string str;
            //1文字=1バイトの場合
            if(byteSize == ByteSize.One)
            {
                dummyChars = new char [bytes.Length];
                for(int i=0;i<bytes.Length;i++)
                {
                    dummyChars[i] = Convert.ToChar(bytes[i]);
                }
            }
            //1文字=2バイトの場合
            else
            {
                dummyChars = new char [(int)(bytes.Length / 2)];
                int allLength =0;
                for(int i=0;i<dummyChars.Length;i++)
                {
                    dummyChars[i] = BitConverter.ToChar(bytes, allLength);
                    allLength += 2;
                } 
            }
            str = new String(dummyChars);
            return str;
        }
		
        //メソッド呼び出し側の処理
        byte [] bytes = {0x61, 0x73, 0x63, 0x69, 0x69};
        string str ;
		
        str = ConvertBytesToStr(bytes, ByteSize.One);    //str = "ascii"
        
        str = "日本語";
        bytes = ConvertStrToBytes(str, ByteSize.Two);    //bytes = {0xE5, 0x65, 0x2C, 0x67, 0x9E, 0x8A}
        str = ConvertBytesToStr(bytes, ByteSize.Two);    //"日本語"
        

ページトップへ




◆環境変数の取得
2004/3/27

ページトップへ

環境変数など,システムの環境情報の取得にはSystem.Environmentクラスを利用します.

        using System;

        string path = Environment.GetEnvironmentVariable("PATH");
        
ページトップへ



◆画面領域の取得
2004/3/27

ページトップへ

画面領域は,以下のプロパティから取得できます.

        using System.Windows.Forms;

        int screenWidth = Screen.PrimaryScreen.Bounds.Width;
        int screenHeight = Screen.PrimaryScreen.Bounds.Height;
        
以下に,指定したwidth, height を比率を保ちながら画面領域以内に縮小するコードを書いてみました.
        //width, heightが画面の表示領域に入っているか調べる.
        //画面の領域より大きければ,画面の領域に収まるように縮小する.
        private bool CheckSize(ref int width, ref int height)
        {
            int screenWidth = Screen.PrimaryScreen.Bounds.Width;
            int screenHeight = Screen.PrimaryScreen.Bounds.Height;
            bool changed = false;

            //widthを画面幅以内に保つ
            if(width > screenWidth)
            {    
                height = (int)(height * ((float)screenWidth / (float)width));
                width = screenWidth;
                changed =true;
            }
            //heightを画面の高さ以内に保つ
            if(height > screenHeight)
            {
                width = (int)(width * ((float)screenHeight / (float)height));
                height = screenHeight;
                changed = true;
            }
            return changed;
        }
      
ページトップへ





◆構造体のメモリレイアウト
2004/3/27

ページトップへ

.Netランタイムにおける構造体のメモリレイアウトには,Auto, Sequential, Explicitの三種類があります. 構造体の定義の前に,StructLayoutという属性を指定することでレイアウトを明示的に指定できます.

Sequentialレイアウトでは,構造体の要素が順番にメモリに配置されます.(C#のデフォルト値)
Explicitレイアウトでは, 構造体の各要素のメモリ配置を[FieldOffset(0)]といった属性を用いて明示的に指定できます.(=共用体として利用できる.)
Autoレイアウトでは,構造体の各要素を.Netランタイムが最適化してメモリに配置します.が,メモリ配置が予測できないため,使わないほうがよいでしょう.

以下に,SequentialレイアウトとExplicitレイアウトを指定する例を示します.

        [StructLayout(LayoutKind.Sequential)]
        public struct Data
        {
            public byte type;
            public long id;
        };
        
        [StructLayout(LayoutKind.Explicit)]
        public struct SampleUnion 
        {
            [FieldOffset(0)] 
            public int i;
            [FieldOffset(0)] 
            public byte b1;
            [FieldOffset(1)] 
            public byte b2;
            [FieldOffset(2)] 
            public byte b3;
            [FieldOffset(3)] 
            public byte b4;
        }

        
また,構造体の中に配列や文字列を利用する場合は,以下のような属性を用います.
        [StructLayout(LayoutKind.Sequential)]
        public struct  ArrayStruct
        {
            [MarshalAs( UnmanagedType.ByValArray, SizeConst=8)] 
            public byte [] bytes; 
        };
        
        [StructLayout(LayoutKind.Sequential)]
        public struct StringStruct
        {
            [MarshalAs(UnmanagedType.LPStr)] 
            public string message;
        };
      
ページトップへ




◆可変個引数の扱い方
2004/3/27

ページトップへ

paramsというキーワードをメソッド宣言に加えることで,可変個の引数リスト(パラメータ)を扱うことができます.(ひとつのメソッド宣言内で一回だけ利用可能.)

下記のように記述すれば,メッセージの出力先を一箇所でまとめて変更できます.

        private void PrintMessage(string m, params object []args)
        {
            string message = String.Format(m, args);
            //Console.WriteLine(message);                          //コンソールアプリケーション用    
            textBoxMessage.Text = message + "\r\n" + textBoxMessage.Text;    //GUIアプリケーション用
        }
        
条件付のprintfのような使い方をするのにも便利です.
        bool Debug = true;
        private void PrintDebug(string m, params object []args)
        {
            if(Debug)
            {
                PrintMessage(m,args);
            }
        }
        private void PrintError(string m, params object []args)
        {
            PrintMessage("Error: " + m, args);
        }
      
ページトップへ


[俄プログラマー心得]