É tão simples quanto:
num1 = num2 = 5;
Ao usar uma propriedade de objeto em vez de variável, é interessante saber que o getacessador do valor intermediário não é chamado. Apenas oset acessador é chamado para todas as propriedades acessadas na sequência de atribuição.
Tomemos, por exemplo, uma classe que grava no console toda vez que o acessador gete seté invocado.
static void Main(string[] args)
{
var accessorSource = new AccessorTest(5);
var accessor1 = new AccessorTest();
var accessor2 = new AccessorTest();
accessor1.Value = accessor2.Value = accessorSource.Value;
Console.ReadLine();
}
public class AccessorTest
{
public AccessorTest(int value = default(int))
{
_Value = value;
}
private int _Value;
public int Value
{
get
{
Console.WriteLine("AccessorTest.Value.get {0}", _Value);
return _Value;
}
set
{
Console.WriteLine("AccessorTest.Value.set {0}", value);
_Value = value;
}
}
}
Isso produzirá
AccessorTest.Value.get 5
AccessorTest.Value.set 5
AccessorTest.Value.set 5
Significando que o compilador atribuirá o valor a todas as propriedades e não o relerá toda vez que for atribuído.
num1 = (num2 = 5)e a primeira atribuição executada (num2 = 5) retorna o valor 5 para o mundo externo - que, por sua vez, é atribuído a num1. Isso funciona ad infinitum (num0 = num1 = num2 = 5).