Вопрос: В чём польза readonly полей в C#?

Лучший ответ: Значения полей readonly можно менять динамически, в конструкторе класса. Последующие изменения невозможны.public class MyClass
{

  public readonly int Value = 0;

  public MyClass()
  {
    this.Value = 123;
  }

}
Для чего это использовать - зависит от фантазии.
Например, можно создать класс User и сделать readonly поле, содержащее идентификатор пользователя. Таким образом, идентификатор пользователя можно будет указать только при инициализации класса и, в последующем, изменить его будет невозможно. Это позволит избежать проблем в логике, которые еще предстоит придумать :-)
Если сравнивать со свойствами и константами, то значения readonly свойств могут меняться в процессе жизненного цикла класса. Значения readonly полей, как уже было сказано ранее, можно указывать только  при объявлении и в конструкторе класса. 
Значения констант являются фиксированными и изменять их программно невозможно. Количество типов для констант ограничено, в то время как поля могут иметь любой тип. Константы являются статическими, а поля не обязаны быть таковыми. В этом плане, readonly поля более гибкие, чем константы.
В дополнение, немного кода (Console Application):using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Timers;
using System.Threading;

namespace ConsoleApplication20
{

  class Program
  {
    static void Main(string[] args)
    {
      // создаем экземпляр класса
      var t = new Toster(199065);
      // выводим значение константы ProjectName
      // доступ только через Toster
      Console.WriteLine(Toster.ProjectName);
      // доступа к локальным константам здесь нет
      // Toster.CounterPattern
      // t.CounterPattern

      // выводим заголовок вопроса
      Console.WriteLine(t.Title);

      // бесконечность - не предел!
      while (true)
      {
        // t.Id = 123;
        // мы не можем поменять идентификатор
        // следовательно, этот код, логически, будет работать правильно

        // выводим число ответов
        Console.WriteLine("Ответов: {0}", t.AnswersCount);

        // t.AnswersCount = 123
        // мы не можем менять значение readonly свойства,
        // но оно может меняться внутри экземпляра класса

        // пауза 10 сек.
        Thread.Sleep(10000);
      }
    }
  }

  public class Toster
  {

    // публичная константа, доступ через Toster
    public const string ProjectName = "Тостер";

    // локальная константа, доступна только в рамках этого класса
    const string CounterPattern = "<span class=\"section-header__counter\" role=\"answers_counter\">";

    // публичные поля только для чтения
    // значение может быть установлено в конструкторе
    public readonly string Title = "Нет данных";
    public readonly int Id = 0;
    
    // свойство только для чтения
    // значение может меняться в процессе жизни экземпляра класса
    // при помощи локальной переменной
    private int _AnswersCount = 0;
    public int AnswersCount
    {
      get
      {
        return _AnswersCount;
      }
    }

    // это просто таймер
    private System.Timers.Timer Timer = null;

    // а это конструктор
, но не Lego

    public Toster(int id)
    {
      if (id <= 0) { return; }
      
      // получаем вопрос
      var web = new WebClient();
      web.Encoding = Encoding.UTF8;
      var result = web.DownloadString(String.Format("https://toster.ru/q/{0}", id));
      // из шаблона url тоже можно сделать константу
      // либо в классе можно сделать readonly свойство, 
      // которое будет на лету формировать итоговый адрес:
      // return String.Format("https://toster.ru/q/{0}", this.Id)

      // устанавливаем значения для полей
      this.Id = id;
      this.Title = WebUtility.HtmlDecode
      ( 
        result.Substring
        (
          result.IndexOf("<title>") + "<title>".Length,
          result.IndexOf("</title>") - result.IndexOf("<title>") - "<title>".Length
        )
      );
      // выдергиваем число ответов на вопрос
      this.ParseAnswersCount(result);

      // запускаем периодическую проверку 
Менделеева

      Timer = new System.Timers.Timer(10000);
      Timer.Elapsed += Timer_Elapsed;
    }
    
    // обработчик истечения интервала времени 
    private void Timer_Elapsed(object sender, EventArgs e)
    {
      var web = new WebClient();
      web.Encoding = Encoding.UTF8;
      var result = web.DownloadString(String.Format("https://toster.ru/q/{0}", this.Id));
      this.ParseAnswersCount(result);
    }

    // выдергиватель количества ответов
    private void ParseAnswersCount(string value)
    {
      int startstart = value.IndexOf("Ответы на вопрос");
      int start = value.IndexOf(CounterPattern, startstart) + CounterPattern.Length;
      int len = value.IndexOf("</span>", start) - start;
      _AnswersCount = Convert.ToInt32(value.Substring(start, len));
    }
    
  }

}

Худший ответ: Константы подставляются на этапе компиляции,
Readonly - в рантайме.
То есть константу нельзя иницииализировать ссылочным типом, Или например прочитать файл и заполнить константу.