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;
}
}
}