O problema fundamental do "void" é que ele não significa a mesma coisa que qualquer outro tipo de retorno. "void" significa "se esse método retornar, ele não retornará nenhum valor". Não nulo; null é um valor. Ele não retorna nenhum valor.
Isso realmente atrapalha o sistema de tipos. Um sistema de tipos é essencialmente um sistema para fazer deduções lógicas sobre quais operações são válidas em valores específicos; um método de retorno nulo não retorna um valor; portanto, a pergunta "que operações são válidas nessa coisa?" não faz nenhum sentido. Não existe "coisa" para que haja uma operação válida ou inválida.
Além disso, isso atrapalha o tempo de execução, algo feroz. O tempo de execução .NET é uma implementação do Sistema de Execução Virtual, que é especificado como uma máquina de empilhar. Ou seja, uma máquina virtual em que todas as operações são caracterizadas em termos de efeito em uma pilha de avaliação. (Obviamente, na prática, a máquina será implementada em uma máquina com pilha e registrador, mas o sistema de execução virtual assume apenas uma pilha.) O efeito de uma chamada para um método nulo é fundamentalmentediferente do efeito de uma chamada para um método não nulo; um método não nulo sempre coloca algo na pilha, que pode precisar ser disparado. Um método nulo nunca coloca algo na pilha. E, portanto, o compilador não pode tratar os métodos void e non-void da mesma maneira no caso em que o valor retornado do método é ignorado; se o método for nulo, não haverá valor de retorno; portanto, não deverá haver pop.
Por todos esses motivos, "nulo" não é um tipo que pode ser instanciado; não tem valores , esse é o seu ponto. Não é convertível em objeto, e um método de retorno nulo nunca pode ser tratado polimorficamente com um método de retorno nulo, porque isso corrompe a pilha!
Portanto, void não pode ser usado como argumento de tipo, o que é uma pena, como você observa. Seria muito conveniente.
Com o benefício da retrospectiva, teria sido melhor para todos os envolvidos se, em vez de nada, um método de retorno vazio retornasse automaticamente "Unit", um tipo de referência único e mágico. Você saberia que toda chamada de método coloca algo na pilha , você sabia que toda chamada de método retorna algo que poderia ser atribuído a uma variável do tipo de objeto e, é claro, Unit poderia ser usado como argumento de tipo , portanto, haveria não é necessário ter tipos de delegação separados de Ação e Func. Infelizmente, esse não é o mundo em que estamos.
Para mais alguns pensamentos nesse sentido, consulte: