File-size format provider

2019-01-08 06:10发布

Is there any easy way to create a class that uses IFormatProvider that writes out a user-friendly file-size?

public static string GetFileSizeString(string filePath)
{
    FileInfo info = new FileInfo(@"c:\windows\notepad.exe");
    long size = info.Length;
    string sizeString = size.ToString(FileSizeFormatProvider); // This is where the class does its magic...
}

It should result in strings formatted something like "2,5 MB", "3,9 GB", "670 bytes" and so on.

11条回答
女痞
2楼-- · 2019-01-08 06:27

Here is an extension with more precision:

    public static string FileSizeFormat(this long lSize)
    {
        double size = lSize;
        int index = 0;
        for(; size > 1024; index++)
            size /= 1024;
        return size.ToString("0.000 " + new[] { "B", "KB", "MB", "GB", "TB" }[index]);          
    }
查看更多
淡お忘
3楼-- · 2019-01-08 06:28

I have taken Eduardo's answer and combined it with a similar example from elsewhere to provide additional options for the formatting.

public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter
{
   public object GetFormat(Type formatType)
   {
      if (formatType == typeof(ICustomFormatter))
      {
         return this;
      }

      return null;
   }

   private const string fileSizeFormat = "FS";
   private const string kiloByteFormat = "KB";
   private const string megaByteFormat = "MB";
   private const string gigaByteFormat = "GB";
   private const string byteFormat = "B";
   private const Decimal oneKiloByte = 1024M;
   private const Decimal oneMegaByte = oneKiloByte * 1024M;
   private const Decimal oneGigaByte = oneMegaByte * 1024M;

   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
      //
      // Ensure the format provided is supported
      //
      if (String.IsNullOrEmpty(format) || !(format.StartsWith(fileSizeFormat, StringComparison.OrdinalIgnoreCase) ||
                                            format.StartsWith(kiloByteFormat, StringComparison.OrdinalIgnoreCase) ||
                                            format.StartsWith(megaByteFormat, StringComparison.OrdinalIgnoreCase) ||
                                            format.StartsWith(gigaByteFormat, StringComparison.OrdinalIgnoreCase)))
      {
         return DefaultFormat(format, arg, formatProvider);
      }

      //
      // Ensure the argument type is supported
      //
      if (!(arg is long || arg is decimal || arg is int))
      {
         return DefaultFormat(format, arg, formatProvider);
      }

      //
      // Try and convert the argument to decimal
      //
      Decimal size;

      try
      {
         size = Convert.ToDecimal(arg);
      }
      catch (InvalidCastException)
      {
         return DefaultFormat(format, arg, formatProvider);
      }

      //
      // Determine the suffix to use and convert the argument to the requested size
      //
      string suffix;

      switch (format.Substring(0, 2).ToUpper())
      {
         case kiloByteFormat:
            size = size / oneKiloByte;
            suffix = kiloByteFormat;
            break;
         case megaByteFormat:
            size = size / oneMegaByte;
            suffix = megaByteFormat;
            break;
         case gigaByteFormat:
            size = size / oneGigaByte;
            suffix = gigaByteFormat;
            break;
         case fileSizeFormat:
            if (size > oneGigaByte)
            {
               size /= oneGigaByte;
               suffix = gigaByteFormat;
            }
            else if (size > oneMegaByte)
            {
               size /= oneMegaByte;
               suffix = megaByteFormat;
            }
            else if (size > oneKiloByte)
            {
               size /= oneKiloByte;
               suffix = kiloByteFormat;
            }
            else
            {
               suffix = byteFormat;
            }
            break;
         default:
            suffix = byteFormat;
            break;
      }

      //
      // Determine the precision to use
      //
      string precision = format.Substring(2);

      if (String.IsNullOrEmpty(precision))
      {
         precision = "2";
      }

      return String.Format("{0:N" + precision + "}{1}", size, suffix);
   }

   private static string DefaultFormat(string format, object arg, IFormatProvider formatProvider)
   {
      IFormattable formattableArg = arg as IFormattable;

      if (formattableArg != null)
      {
         return formattableArg.ToString(format, formatProvider);
      }

      return arg.ToString();
   }
}
查看更多
看我几分像从前
4楼-- · 2019-01-08 06:35

If you change:

      if (String.IsNullOrEmpty(precision))
      {
         precision = "2";
      }

into

      if (String.IsNullOrEmpty(precision))
      {
        if (size < 10)
        {
           precision = "2";
        }
        else if (size < 100)
        {
            precision = "1";
        }
        else
        {
           precision = "0";
        }
      }

the results without additional precision specifier (so just 0:fs instead of 0:fs3) will start to mimic Win32's StrFormatByteSize() by adjusting precision to size.

查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-01-08 06:36

OK I'm not going to wrap it up as a Format provider but rather than reinventing the wheel there's a Win32 api call to format a size string based on supplied bytes that I've used many times in various applications.

[DllImport("Shlwapi.dll", CharSet = CharSet.Auto)]
public static extern long StrFormatByteSize( long fileSize, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer, int bufferSize );

So I imagine you should be able to put together a provider using that as the core conversion code.

Here's a link to the MSDN spec for StrFormatByteSize.

查看更多
别忘想泡老子
6楼-- · 2019-01-08 06:40

I needed a version that can be localized for different cultures (decimal separator, "byte" translation) and support for all possible binary prefixes (up to Exa). Here is an example that demonstrates how to use it:

// force "en-US" culture for tests
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(1033); 

// Displays "8.00 EB"
Console.WriteLine(FormatFileSize(long.MaxValue)); 

// Use "fr-FR" culture. Displays "20,74 ko", o is for "octet"
Console.WriteLine(FormatFileSize(21234, "o", null, CultureInfo.GetCultureInfo(1036)));

And here is the code:

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes or exabytes, depending on the size
    /// </summary>
    /// <param name="size">The size.</param>
    /// <returns>
    /// The number converted.
    /// </returns>
    public static string FormatFileSize(long size)
    {
        return FormatFileSize(size, null, null, null);
    }

    /// <summary>
    /// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes or exabytes, depending on the size
    /// </summary>
    /// <param name="size">The size.</param>
    /// <param name="byteName">The string used for the byte name. If null is passed, "B" will be used.</param>
    /// <param name="numberFormat">The number format. If null is passed, "N2" will be used.</param>
    /// <param name="formatProvider">The format provider. May be null to use current culture.</param>
    /// <returns>The number converted.</returns>
    public static string FormatFileSize(long size, string byteName, string numberFormat, IFormatProvider formatProvider)
    {
        if (size < 0)
            throw new ArgumentException(null, "size");

        if (byteName == null)
        {
            byteName = "B";
        }

        if (string.IsNullOrEmpty(numberFormat))
        {
            numberFormat = "N2";
        }

        const decimal K = 1024;
        const decimal M = K * K;
        const decimal G = M * K;
        const decimal T = G * K;
        const decimal P = T * K;
        const decimal E = P * K;

        decimal dsize = size;

        string suffix = null;
        if (dsize >= E)
        {
            dsize /= E;
            suffix = "E";
        }
        else if (dsize >= P)
        {
            dsize /= P;
            suffix = "P";
        }
        else if (dsize >= T)
        {
            dsize /= T;
            suffix = "T";
        }
        else if (dsize >= G)
        {
            dsize /= G;
            suffix = "G";
        }
        else if (dsize >= M)
        {
            dsize /= M;
            suffix = "M";
        }
        else if (dsize >= K)
        {
            dsize /= K;
            suffix = "k";
        }
        if (suffix != null)
        {
            suffix = " " + suffix;
        }
        return string.Format(formatProvider, "{0:" + numberFormat + "}" + suffix + byteName, dsize);
    }
查看更多
登录 后发表回答