开发者

Why does System.IO.Path.Combine have 4 overloads?

In .NET 4, System.IO.Path has the following overloads for the Combine method:

public static string Combine(params string[] paths)
public static string Combine(string path1, string path2)
public static string Combine(string path1, string path2, string path3)
public static string Combine(string path1, string path2, string path3, string path4)

The first one was added in .NET 4 to support any number of path arguments. The second one was already there in earlier versions so I suppose it is kept for backwards compatibility.

But I'm curious what the use of the other overloads is. Aren't these use cases already covered by the first method signature with params?

edit: I now believe that the answer is "because not all la开发者_如何学Gonguages have params support (and passing an array without params support is inconvenient)". However, the stackoverflow hive mind seems to disagree strongly. Therefore, as a compromise, I am not accepting any answer.


I would suspect for performance as you have to create an intermediary array with params, plus the overhead of traversing the array, etc. There are probably some internal, etc, cases where there is a good case for using the fixed numbered parameter versions.

Update

I have now carried out performance tests to check my assumptions. This is something I should have done in the first place - I broke my own performance mantra:

Don't think - measure.

My assumptions are not entirely correct, but not entirely wrong. The 4 fixed parameter version is marginally slower than the 4 params version, but the 3 and 2 fixed variations perform significantly better.

There are a number of issues with the performance test harness for the current accepted answer which states that performance goes entirely in favour of the params version - this is incorrect:

  • It uses DateTime.Now for timing - always use a Stopwatch for micro benchmarking as DatTime.Now is only accurate to between ~10ms->15ms. There are endless articles on this.
  • The test only covers the case of the 4 parameter version - what about the 3 and 2 parameter versions?
  • Garbage generation and collection are not taken into consideration. One method might be faster in a straight line between A->B, but it might also generate a lot of garbage which will have to be cleaned up at some stage. This is a deferred performance penalty, but it is still a performance impact so should be taken into consideration.
  • Ensure arguments have realistic values - is combining single character paths realistic?

I have the following performance results which I have included 2, 3 and 4 argument variations where it can be seen that performance is significantly better for 2 and 3 variations, and marginally worse for the 4 variation. The fixed number argument versions are faster on the whole though, with 3 being the most significant in terms of this question (the 2 argument variation existed since .Net 1.1).

***2 Args***
params2:3018.44ms
params2:3007.61ms
params2:2988.52ms
params2:2992.33ms
params2:2995.89ms
args2  :1724.83ms
args2  :1723.97ms
args2  :1727.76ms
args2  :1720.42ms
args2  :1718.24ms
***3 Args***
params3:4168.37ms
params3:4169.61ms
params3:4165.63ms
params3:4161.51ms
params3:4153.61ms
args3  :3476.96ms
args3  :3483.40ms
args3  :3482.49ms
args3  :3595.15ms
args3  :3561.11ms
***4 Args***
params4:4992.71ms
params4:4985.51ms
params4:4995.63ms
params4:5002.47ms
params4:4993.99ms
args4  :4993.02ms
args4  :4992.93ms
args4  :4991.07ms
args4  :4993.04ms
args4  :4995.14ms

Test:

public void MeasurePathPerformance()
{
    const int TestIterations = 5;
    const string Root = "C:\\xxxxxxxxxx";
    string seg = new string('x', 10);
    string path = null;

    Action<string, Func<double>> test = (name, action) =>
    {
        for (int i = 0; i < TestIterations; i++)
        {
            Console.WriteLine("{0}:{1:F2}ms", name, action());
        }
    };

    Console.WriteLine("***2 Args***");
    Action p2 = () => path = Path.Combine(new[] { Root, seg });
    test("params2", () => TimeTest(p2));
    Action a2 = () => path = Path.Combine(Root, seg);
    test("args2  ", () => TimeTest(a2));

    Console.WriteLine("***3 Args***");
    Action p3 = () => path = Path.Combine(new[] { Root, seg, seg });
    test("params3", () => TimeTest(p3));
    Action a3 = () => path = Path.Combine(Root, seg, seg);
    test("args3  ", () => TimeTest(a3));

    Console.WriteLine("***4 Args***");
    Action p4 = () => path = Path.Combine(new[] { Root, seg, seg, seg });
    test("params4", () => TimeTest(p4));
    Action a4 = () => path = Path.Combine(Root, seg, seg, seg);
    test("args4  ", () => TimeTest(a4));

    Console.WriteLine(path);
}

[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
private static double TimeTest(Action action)
{
    const int Iterations = 10 * 1000 * 1000;

    Action gc = () =>
    {
        GC.Collect();
        GC.WaitForFullGCComplete();
    };

    Action empty = () => { };

    Stopwatch stopwatch1 = Stopwatch.StartNew();

    for (int j = 0; j < Iterations; j++)
    {
        empty();
    }

    double loopElapsed = stopwatch1.Elapsed.TotalMilliseconds;

    gc();

    action(); //JIT
    action(); //Optimize

    Stopwatch stopwatch2 = Stopwatch.StartNew();

    for (int j = 0; j < Iterations; j++)
    {
        action();
    }

    gc();

    double testElapsed = stopwatch2.Elapsed.TotalMilliseconds;

    return (testElapsed - loopElapsed);
}


One possible reason can also be to reduce pressure on the garbage collector. The params-array overload creates a new array each time the method is called. If the method is called often, a lot of temporary array objects are created, increasing pressure on the garbage collector.


What about syntactic shugar for languages that do not support something similar to the c#-"params" keyword

Update

Removed Performance issue since i measured only the 4 parameter version but not the 3 and 2 parameter version that are realy faster

The performance is not the reason. (See my benchmark below)

Benchmark

I measured the performance. To my surprise the array version was slightly better.

    [TestMethod]
    public void MeasurePathPerfomance()
    {
        // repeat several times to avoid jit-issues
        for (int j = 0; j < 10; j++)
        {
            {
                DateTime start = DateTime.Now;

                string result;
                for (int i = 0; i < 30000; i++)
                {
                    result = System.IO.Path.Combine("a", "b", "c", "d"); // use 4 parameter version
                }
                TimeSpan spend = DateTime.Now - start;
                System.Console.WriteLine("4 param : {0}", spend.TotalMilliseconds);
            }
            {
                DateTime start = DateTime.Now;

                string result;
                for (int i = 0; i < 30000; i++)
                {
                    result = System.IO.Path.Combine(new string[] { "a", "b", "c", "d" });
                }
                TimeSpan spend = DateTime.Now - start;
                System.Console.WriteLine("array[4] param : {0}", spend.TotalMilliseconds);
            }
        }
    }

result

    4 param : 10.001
    array[4] param : 9.0009
    4 param : 12.0012
    array[4] param : 8.0008
    4 param : 12.0012
    array[4] param : 10.001
    4 param : 11.0011
    array[4] param : 9.0009
    4 param : 11.0011
    array[4] param : 11.0011
    4 param : 11.0011
    array[4] param : 9.0009
    4 param : 10.001
    array[4] param : 8.0008
    4 param : 10.001
    array[4] param : 9.0009
    4 param : 11.0011
    array[4] param : 9.0009
    4 param : 11.0011
    array[4] param : 9.0009


Backwards compatibility is the best reason I can think of. The overloads are probably all calling the first method.


Its just because of performance, the performance of the last 3 is greater then the first method.

If you want to know the implementation, just look at mscorlib with reflector, you'll see the performance will be better in the last 3 functions


I think the reason is that most of programmers use one, two, three or four values to combine and more than that you want to think its better to implement an array rather to use more values.

Example

Sum(a , b); //fine
Sum(a , b , c);//fine
Sum(a , b , c , d);//fine
Sum(a , b , c , d ,....); //now I think you think everyone think  even Microsoft also thinks, its better to implement array here

// something like this
Sum(params var[] n);   

So you will found most of the method holds 1,2,3,4 arguments and then params

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜