Based on apomene's solution I'd opt for a (real) timer based solution, since Thread.Sleep is pretty imprecise.
static void PrintSlowly(string print)
{
    int index = 0;
    System.Timers.Timer timer = new System.Timers.Timer();
    timer.Interval = 100;
    timer.Elapsed += new System.Timers.ElapsedEventHandler((sender, args) =>
    {
        if (index < print.Length)
        {
            Console.Write(print[index]);
            index++;
        }
        else
        {
            Console.Write("\n");
            timer.Enabled = false;
        }
    });
    timer.Enabled = true;
}
The Timer will come back every 100 milliseconds, pick the next character and print it. If no more characters are available, it prints return and disables itself. I wrote it using an anonymous handling method using a lambda-expression - not the cleanest way. It's just about the principle.
This implementation runs complete in parallel to your application, so it does not block your code-execution. If you want that, a different approach may be better.
Alternatively - as a modification of apomene's solution without busy-wait - you can use a ManualResetEvent.
static System.Timers.Timer delay = new System.Timers.Timer();
static AutoResetEvent reset = new AutoResetEvent(false);
private static void InitTimer()
{
    delay.Interval = 100;
    delay.Elapsed += OnTimedEvent;
    delay.Enabled = false;
}
private static void OnTimedEvent(object sender, ElapsedEventArgs e)
{
    ((System.Timers.Timer)sender).Enabled = false;
    reset.Set();
}
static void PrintSlowly2(string print)
{
    InitTimer();
    foreach (char l in print)
    {
        Console.Write(l);
        delay.Enabled = true;
        reset.WaitOne();
    }
    Console.Write("\n");
}
It waits using a AutoResetEvent, so other applications/threads can use the processor!