Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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 languages 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.

like image 558
Wim Coenen Avatar asked Dec 16 '10 16:12

Wim Coenen


1 Answers

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); } 
like image 126
Tim Lloyd Avatar answered Dec 20 '22 01:12

Tim Lloyd