Considere esta hierarquia de classes simplificada:
public interface IProcessor
{
Task<TProcessor> Run<TProcessor>() where TProcessor : IProcessor;
}
public abstract class Processor : IProcessor
{
public async Task<TProcessor> Run<TProcessor>() where TProcessor : IProcessor
{
await DoWork();
return (TProcessor)(object)this; // works, but yuck
//return (TProcessor)this; // error: "Cannot convert type 'Processor' to 'TProcessor'"
}
protected abstract Task DoWork();
}
public sealed class FooProcessor : Processor
{
protected override Task DoWork() => Task.CompletedTask;
}
O (TProcessor)(object)this
truque funciona, mas é um truque feio de compilador.
Posso modificar este código para depender de verificações em tempo de compilação?
Um erro de tempo de execução é possível se você tiver outra classe herdando de
Processor
, digamosBarProcessor
. EntãoBarProcessor p = new FooProcessor().Run<BarProcessor>();
será compilado, mas falhará em tempo de execução.O C# não suporta um tipo "self" como este (veja por exemplo https://github.com/dotnet/csharplang/issues/5413 ), mas o código pode ser melhorado movendo o parâmetro de tipo do método para o tipo. Isso restringe o potencial de erros nas implementações de classe e reduz a necessidade de especificar parâmetros de tipo no site da chamada.
Por exemplo:
Agora, os chamadores de
Run
não podem especificar um parâmetro de tipo errado (já que não há nenhum), masFooProcessor
ainda podem herdar erroneamente deProcessor<BarProcessor>
. Além disso, a interfaceIProcessor<TProcessor>
agora é claramente útil apenas como uma restrição (where T: IProcessor<T>
), não como um tipo.Note que
FooProcessor
eBarProcessor
são selados. Permitir herança de umaProcessor<TProcessor>
subclasse exigirá que essa subclasse seja genérica, comopublic abstract class SpecialProcessor<TProcessor> : Processor<TProcessor> where TProcessor : SpecialProcessor<TProcessor>
.