Um dos problemas seria que, em muitos casos, a chave da tabela de hash seria uma string. Portanto, os consumidores do método precisariam saber de antemão quais chaves usar para extrair os dados. Isso daria o potencial de erros devido a erros de ortografia ao acessar os dados.
Outra desvantagem é a refatorabilidade. Se você decidir mais tarde alterar o nome de um membro, terá várias sequências mágicas que também precisam ser alteradas. É muito mais simples renomear um membro da classe usando as ferramentas de refatoração fornecidas pela maioria dos bons IDE. Com uma tabela de hash, você provavelmente teria que fazer uma operação de localização / substituição em todos os arquivos de origem, o que poderia ser problemático.
Por fim, você perderá a verificação do tempo de compilação do acesso do membro - em termos de nome e tipo. O último não é um problema se a sua tabela de hash contiver apenas um tipo de objeto, mas se ela contiver muitos (mesmo na mesma cadeia hierárquica), você realmente deseja aproveitar o sistema de tipos do seu idioma e obter a verificação do tempo de compilação lá. Na maioria dos IDE, você terá algum tipo de recurso intellisense / autocomplete - eles funcionam analisando o sistema de tipos, mas não poderão ajudá-lo com as chaves da tabela de hash.
Quanto aos momentos em que seria apropriado retornar uma tabela de hash (ou outra coleção de pares de valores-chave), você usaria isso quando os valores e as chaves não fossem conhecidos no momento da compilação. Por exemplo, se você tiver um método que analise uma string de consulta e retorne as chaves e os valores correspondentes, uma tabela de hash seria uma boa opção. Nesse caso, você também gostaria de pensar em retornar algum tipo de tabela de hash imutável ou apenas de leitura.
Editar - A maioria dos pontos levantados nesta resposta deixa de ser aplicada quando você está falando sobre linguagens dinâmicas :)