String.Format() で動作する何かを実際に求めていたことに気づきました-投稿する前に質問を2回読むべきだったと思います;-)
毎回フォーマット プロバイダーを明示的に渡す必要があるソリューションは好きではありません。この記事から収集できることから、これにアプローチする最善の方法は、FileSize 型を実装し、IFormattable インターフェイスを実装することです。
このインターフェイスをサポートし、整数からキャストできる構造体を実装しました。私自身のファイル関連 API では、.FileSize プロパティが FileSize インスタンスを返すようにします。
コードは次のとおりです。
using System.Globalization;
public struct FileSize : IFormattable
{
private ulong _value;
private const int DEFAULT_PRECISION = 2;
private static IList<string> Units;
static FileSize()
{
Units = new List<string>(){
"B", "KB", "MB", "GB", "TB"
};
}
public FileSize(ulong value)
{
_value = value;
}
public static explicit operator FileSize(ulong value)
{
return new FileSize(value);
}
override public string ToString()
{
return ToString(null, null);
}
public string ToString(string format)
{
return ToString(format, null);
}
public string ToString(string format, IFormatProvider formatProvider)
{
int precision;
if (String.IsNullOrEmpty(format))
return ToString(DEFAULT_PRECISION);
else if (int.TryParse(format, out precision))
return ToString(precision);
else
return _value.ToString(format, formatProvider);
}
/// <summary>
/// Formats the FileSize using the given number of decimals.
/// </summary>
public string ToString(int precision)
{
double pow = Math.Floor((_value > 0 ? Math.Log(_value) : 0) / Math.Log(1024));
pow = Math.Min(pow, Units.Count - 1);
double value = (double)_value / Math.Pow(1024, pow);
return value.ToString(pow == 0 ? "F0" : "F" + precision.ToString()) + " " + Units[(int)pow];
}
}
そして、これがどのように機能するかを示す簡単な単体テスト:
[Test]
public void CanUseFileSizeFormatProvider()
{
Assert.AreEqual(String.Format("{0}", (FileSize)128), "128 B");
Assert.AreEqual(String.Format("{0}", (FileSize)1024), "1.00 KB");
Assert.AreEqual(String.Format("{0:0}", (FileSize)10240), "10 KB");
Assert.AreEqual(String.Format("{0:1}", (FileSize)102400), "100.0 KB");
Assert.AreEqual(String.Format("{0}", (FileSize)1048576), "1.00 MB");
Assert.AreEqual(String.Format("{0:D}", (FileSize)123456), "123456");
// You can also manually invoke ToString(), optionally with the precision specified as an integer:
Assert.AreEqual(((FileSize)111111).ToString(2), "108.51 KB");
}
ご覧のとおり、FileSize 型を正しくフォーマットできるようになりました。また、必要に応じて通常の数値フォーマットを適用するだけでなく、小数点以下の桁数を指定することもできます。
たとえば、「{0:KB}」などの明示的な形式の選択を許可して、キロバイトでの書式設定を強制するなど、これをさらに進めることができると思います。しかし、私はこれでそれを残すつもりです。
また、これら 2 人が書式設定 API を使用しないことを好むため、最初の投稿を以下に残します...
猫の皮を剥ぐ 100 通りの方法がありますが、これが私のアプローチです - int 型に拡張メソッドを追加します:
public static class IntToBytesExtension
{
private const int PRECISION = 2;
private static IList<string> Units;
static IntToBytesExtension()
{
Units = new List<string>(){
"B", "KB", "MB", "GB", "TB"
};
}
/// <summary>
/// Formats the value as a filesize in bytes (KB, MB, etc.)
/// </summary>
/// <param name="bytes">This value.</param>
/// <returns>Filesize and quantifier formatted as a string.</returns>
public static string ToBytes(this int bytes)
{
double pow = Math.Floor((bytes>0 ? Math.Log(bytes) : 0) / Math.Log(1024));
pow = Math.Min(pow, Units.Count-1);
double value = (double)bytes / Math.Pow(1024, pow);
return value.ToString(pow==0 ? "F0" : "F" + PRECISION.ToString()) + " " + Units[(int)pow];
}
}
アセンブリでこの拡張子を使用すると、ファイルサイズをフォーマットするには、(1234567).ToBytes() のようなステートメントを使用するだけです。
次の MbUnit テストは、出力がどのように見えるかを正確に明らかにします。
[Test]
public void CanFormatFileSizes()
{
Assert.AreEqual("128 B", (128).ToBytes());
Assert.AreEqual("1.00 KB", (1024).ToBytes());
Assert.AreEqual("10.00 KB", (10240).ToBytes());
Assert.AreEqual("100.00 KB", (102400).ToBytes());
Assert.AreEqual("1.00 MB", (1048576).ToBytes());
}
また、単位と精度を必要に応じて簡単に変更できます:-)