Porque os operadores em C# são sempre static?
Porque os operadores em C# são sempre static? No C++ um operador sobrecarregado pode ser implementado por métodos static, instanciados ou virtual. Há alguma razão para esta regra no C#?
Antes de irmos direto ao assunto, há um ponto que vale a pena discutirmos um pouco. Raymond Chen imediatamente disse que tínhamos que voltar um pouco para o passado do C#. O design da linguagem C# não é um processo de subtração. C# não é um Java/C++ com as partes que não se ligavam removidas.
A questão que devemos nos perguntar sobre uma característica potencial da linguagem é "os benefícios trazidos irão justificar os custos?". E os custos são consideravelmente mais que somente os dólares gastos com designing, desenvolvimento, testes, documentação e manutenção. Há mais pequenos custos, como, este recurso tornará mais difícil a troca do algoritmo de inferência de tipo no futuro? Será que isso nos levará a um mundo onde não poderemos fazer alterações sem introduzir incompatibilidades? E por ai vai...
Neste caso específico, os benefícios são pequenos. Se você quer ter um operador virtual sobrecarregado no C# você pode construir um contornando as partes static. Por exemplo:
public class B {
public static B operator+(B b1, B b2) { return b1.Add(b2); }
protected virtual B Add(B b2) { // ...
E você terá ele. Então, os benefícios são pequenos. Mas os custos são grandes. Operadores instanciados no estilo C++ são esquisitos. Por exemplo, eles quebram a simetria. Se você definir um operador + que recebe um C e um int, então c+2 é válido mas 2+c não é, isso que rompe nosso conceito sobre como o operador de adição deve se comportar.
Similarmente, com operadores virtual no C++, o argumento à esquerda é o que serve de parâmetro para chamada virtual. Então novamente, nós temos uma assimetria entre os lados esquerdo e direito. Realmente o que você quer para a maioria dos operadores binários é que sejam comutativos -- você quer que o operador seja virtualizado nos tipos de ambos argumentos, não somente o do lado esquerdo. Mas nem o C# nem C++ suportam comutação nativamente. (Muitos problemas do mundo real devem ser resolvidos se tivermos comutação; por uma coisa, o visitante padrão torna-se trivial.)
E finalmente, no C++ você pode somente definir um operador sobrecarregado sobre um tipo que não seja um ponteiro, isto é um só pode definir para tipos por valor. Isto quer dizer que quando você ver c+2 e reescrever como c.operator+(2), você está garantindo que c não é um ponteiro nulo porque ele nunca será um ponteiro! C# também faz a distinção entre valores e referência, mas seria muito estranho se um operador instanciado sobrecarregado fosse apenas definido sobre tipos por valor "non-nullable", e também seria estranho se c+2 pudesse gerar uma exceção "null reference".
Por isso foi decidido não adicionar operadores sobrecarregados por instância ou virtual no C#.