Utils/NaturalStringComparerUtil.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace ChatworkBulkSender.Utils
{
    class NaturalStringComparerUtil : IComparer<object>
    {
        /// <summary>
        /// 内部静的クラス。
        /// ネイティブメソッドを安全に扱う為のクラス。
        /// [SuppressUnmanagedCodeSecurity] わずかなパフォーマンス向上が見込めるが、セキュリティリスクがある為付与しないこと。
        /// </summary>
        internal static class SafeNativeMethods
        {
            // DllImport属性でP/InvokeでネイティブDLLの関数をインポートする
            // インポート時に文字列をUnicodeとして扱うよう指定
            [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]

            // pszはpointerStringZero-terminated(null終端文字列へのポインタ)を意味する
            // externはこのメソッドが外部DLLに存在することを意味する
            public static extern int StrCmpLogicalW(string psz1, string psz2);
        }

        // 読み取り専用
        private readonly bool _descending;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="descending"></param>
        public NaturalStringComparerUtil(bool descending = false)
        {
            this._descending = descending;
        }

        /// <summary>
        /// 2つのオブジェクトを自然順で比較する。
        /// </summary>
        /// <param name="firstVal"></param>
        /// <param name="secondVal"></param>
        /// <returns></returns>
        public int Compare(object firstVal, object secondVal)
        {
            // NULL値処理
            if (firstVal == null && secondVal == null) { return 0; }

            if (firstVal == null) { return _descending ? 1 : -1; }

            if (secondVal == null) { return _descending ? -1 : 1; }

            // 文字列であるかどうか
            var firstStr = firstVal as string;
            var secondStr = secondVal as string;

            // どちらも文字列の場合
            if (firstStr != null && secondStr != null)
            {
                // 空文字列処理
                if (string.IsNullOrEmpty(firstStr) && string.IsNullOrEmpty(secondStr)) { return 0; }

                if (string.IsNullOrEmpty(firstStr)) { return _descending ? 1 : -1; }

                if (string.IsNullOrEmpty(secondStr)) { return _descending ? -1 : 1; }

                // ネイティブDLLからインポートしたWindowsAPIでの自然順ソート
                int result = SafeNativeMethods.StrCmpLogicalW(firstStr, secondStr);

                // 降順がtrueなら反転させるため負にする
                return _descending ? -result : result;
            }

            // 片方だけが文字列の場合
            if (firstStr != null || secondStr !=null)
            {
                // 文字列表現に変換し、比較する
                return CompareAsString (firstVal, secondVal);
            }

            // どちらも文字列でない場合
            if (firstVal is IComparable comparableFirst)
            {
                try
                {
                    // 同じ型であれば比較する
                    int result = comparableFirst.CompareTo(secondVal);

                    // 降順がtrueなら反転させるため負にする
                    return _descending ? -result : result;
                }
                catch
                {
                    // 型が異なる場合、文字列に変換して比較する
                    return CompareAsString(firstVal, secondVal);
                }
            }
            else
            {
                // IComparebleでない場合、文字列として比較する
                return CompareAsString(firstVal,secondVal);
            }

        }

        /// <summary>
        /// 文字列に変換した後、ネイティブメソッドで比較する。
        /// </summary>
        /// <param name="firstVal"></param>
        /// <param name="secondVal"></param>
        /// <returns></returns>
        private int CompareAsString(object firstVal, object secondVal)
        {
            string firstString = firstVal.ToString();
            string secondString = secondVal.ToString();

            int result = SafeNativeMethods.StrCmpLogicalW(firstString, secondString);

            // 降順がtrueなら反転させるため負にする
            return _descending ? -result : result;
        }
    }
}