views:

104

answers:

5

I have a silly, little class "FileSystemSize" which can be used both as an object and also via public, static methods. The output is similar, but not identical in each case.

The class was intially static, but I added the possibility to initialize it as an object to allow extending with new "convenience methods" in future versions, without the need for a lot of parameter parsing. For example I have GetKBString(), GetMBString(), etc...methods to allow getting the file size conveniently formatted the way I want it (as a string). Internally the class stores the file byte size as a double.

I am a bit confused if this makes sense at all. It seems like I should perhaps split this into a static version and an object version like Microsoft does for Directory and DirectoryInfo. However it just seems easier to me to have this all in one place with a name that can't be mistaken - it should be clear what FileSystemSize does? Are there any implications for maintenance that I am not anticipating? What's that smell?

var mypath = @"C:\mypath";

var filesystemsize = new FileSystemSize(mypath);
string kilobytes = filesystemsize.GetKBString();
string megabytes = filesystemsize.GetMBString();
double bytes = filesystemsize.ByteSize;

double staticbytes = FileSystemSize.GetDirectoryBytesSize(new DirectoryInfo(mypath));
double statickilobytes = FileSystemSize.ConvertSize(staticbytes, "KB");
+1  A: 

A good test: if you're asking yourself and us whether it is okay, there is a chance it is not.

It may not be natural for users of the class to have some methods accessible via class and others via object, especially when the second do not really require the instance properties of the class. Chances are they will be confused and will be referring to like: "WTF this programmer did that?!".

I suggest going with all instance methods if you like the possibility to extend the class, either with extension methods or via subclassing.

Developer Art
+1  A: 

Since you're not having a lot of state in FileSystemSize, isn't this the perfect candidate for extension methods?

I'd personally provide extensions to format numbers as file size strings and use an enum to specify how to format the file sizes:

public static class FileSystemSize
{
    public static long GetDirectoryBytesSize(string path);
}

public static class NumberExtensions
{
    public static string FormatAsFileSize(
        this long fileSize, FileSizeStringFormat format);
}

public enum FileSizeStringFormat
{
    KiloByte,
    MegaByte,
}
dtb
I like that - extension methods rock. Am I wrong in assuming I can't use them if I target .NET 2.0 though?
Glytzhkof
http://stackoverflow.com/questions/707145 http://stackoverflow.com/questions/783155
dtb
I'd be still doing it this way, even just with static methods.
dtb
See http://kohari.org/2008/04/04/extension-methods-in-net-20/ for a quick workaround to get extension methods up and running in .NET 2.0 applications.
David Andres
A: 

Standard smell is the usage of static methods - this makes it hard to maintain in case you use those methods all over your code.

Another smell imho is: class name is not clear about what it actually does. From your description it is meant to format data in which case I would mention it in the class name.

Mikeon
+1  A: 

If you're using C# 3.0, your intentions may be better expressed with extension methods and IFormatProviders. In code, this could be an extension method on the FileInfo and DirectoryInfo ToString methods, so they would read something like this:

var directorySize = myDirectory.ToString("GB");
var fileSize = myFile.ToString("MB");

The above code feels more natural for what you are trying to do.

See how the following works for you. Some of it will need to be tested (the recursive method DirectoryInfoExtender.GetDirectorySize comes to mind). If you need to be able to write statements like Console.WriteLine("{0:GB}", fileInfo), you might also consider writing an IFormatProvider as well.

Also note that I have intentionally skimped on null checks and exception handling for these publically accessible methods.

public static class DirectoryInfoExtender
{
    public static string ToString(this DirectoryInfo d, string format, int fractionalDigits)
    {
        double fileSize = GetDirectorySize(d);
        return FileSizeConverter.GetFileSizeString(fileSize, format, fractionalDigits);
    }

    public static double GetDirectorySize(DirectoryInfo d)
    {    
        var files = d.GetFiles();
        var directories = d.GetDirectories();

        if(files.Length == 0 && directories.Length == 0)
        {
            return 0;
        }
        else
        {
            double size = 0;

            foreach(var file in files)
            {
                size += file.Length;
            }

            foreach(var directory in directories)
            {
                size += GetDirectorySize(directory);
            }
        }

        return size;
    }
}


public static class FileInfoExtender
{
    public static string ToString(this FileInfo f, string format, int fractionalDigits)
    {
        return FileSizeConverter.GetFileSizeString(f.Length, format, fractionalDigits);
    }
}

public class FileSizeConverter
{
    public static string GetFileSizeString(double fileSize, string format, int fractionalDigits)
    {
        long divisor;
        string sizeIndicator;

        switch(format.ToLower().Trim())
        {
            case "gb":
                divisor = (long)Math.Pow(2, 30);
                sizeIndicator = "gigabytes";
                break;
            case "mb":
                divisor = (long) Math.Pow(2, 20);
                sizeIndicator = "megabytes";
                break;
            case "kb":
                divisor = (long)Math.Pow(2, 10);
                sizeIndicator = "kilobytes";
                break;
            default:
                divisor = 1;
                sizeIndicator = "bytes";
                break;
        }

        return String.Format("{0:N" + fractionalDigits +"} {1}", fileSize / divisor, sizeIndicator);
    }
}
David Andres
+2  A: 

Look at it another way: Why are you putting String/Number/UI formatting methods in your FileSystemSize method?

While it can be used in respect of files, this is a general piece of functionality that IMHO should be found elsewhere in a well organised library - just as Path functions are not part of the File or Directory classes in .net, I would put the "format a number" methods in a string or maths utils class.

Separate the responsibilities of your objects and you may find there is no need to mix static and non-static members in cases like this.

Jason Williams