Folks,
I was looking at this thread, trying to solve a problem that doesn't actually exist, because C# "just passes through" a params array! Which I didn't know until I just tried it.
Here's an SSCCE:
using System;
using System.Diagnostics; // for Conditional compilation of method CONTENTS
namespace ConsoleApplication3
{
    public static class Log
    {
        [Conditional("DEBUG")] // active in Debug builds only (a no-op in Release builds)
        public static void Debug(string format, params object[] parms) {
            Console.WriteLine(format, parms); 
            // calls Console.WriteLine(string format, params object[] arg);
            // which I presume calls String.Format(string format, params object[] arg);
            // (Sweet! just not what I expected ;-)
        }
    }
    class Program //LogTest
    {
        static void Main(string[] args) {
            Log.Debug("args[0]={0} args[1]={1}", "one", "two");
            Console.Write("Press any key to continue . . .");
            Console.ReadKey();
        }
    }
}
Produces:
args[0]=one args[1]=two
Sweet!
But why? ... Well because (of course) the closest parameter-match to the heavily-overloaded Console.WriteLine method is (string format, params object[] arg) ... not (string format, object arg) as I was thinking.
I sort-of knew this had to be possible somehow, because (I presume) Console.WriteLine does it, I just somehow expected it to be HARD... and therefore think that the simplicity and "niceness" of this trick-of-the-language is note worthy. 
CSharpLanguageDesigners.ToList().ForEach(dude=>dude.Kudos++);
Cheers. Keith.
PS: I wonder if VB.NET behaves the same way? I suppose it must.