Como você pode exigir um construtor sem parâmetros para tipos que implementam uma interface?

StackOverflow https://stackoverflow.com/questions/26903

Pergunta

Há algum caminho?

Preciso que todos os tipos que implementam uma interface específica tenham um construtor sem parâmetros, isso pode ser feito?

Estou desenvolvendo o código base para outros desenvolvedores da minha empresa usarem em um projeto específico.

Existe um processo que criará instâncias de tipos (em threads diferentes) que executam determinadas tarefas, e preciso que esses tipos sigam um contrato específico (logo, a interface).

A interface será interna à montagem

Se você tiver uma sugestão para este cenário sem interfaces, terei prazer em levá-la em consideração...

Foi útil?

Solução

Juan Manuel disse:

essa é uma das razões pelas quais não entendo porque não pode fazer parte do contrato na interface

É um mecanismo indireto.O genérico permite "trapacear" e enviar informações de tipo junto com a interface.O importante a lembrar aqui é que a restrição não está na interface com a qual você está trabalhando diretamente.Não é uma restrição na interface em si, mas em algum outro tipo que irá "acompanhar" a interface.Esta é a melhor explicação que posso oferecer, infelizmente.

Para ilustrar esse fato, vou apontar uma falha que notei no código do aku.É possível escrever uma classe que compile bem, mas falhe em tempo de execução quando você tenta instanciá-la:

public class Something : ITest<String>
{
  private Something() { }
}

Algo deriva de ITest<T>, mas não implementa nenhum construtor sem parâmetros.Ele irá compilar bem, porque String implementa um construtor sem parâmetros.Novamente, a restrição está em T e, portanto, em String, em vez de ITest ou Something.Como a restrição em T é satisfeita, isso será compilado.Mas falhará em tempo de execução.

Prevenir alguns instâncias deste problema, você precisa adicionar outra restrição a T, conforme abaixo:

public interface ITest<T>
  where T : ITest<T>, new()
{
}

Observe a nova restrição:T:ITest<T>.Esta restrição especifica que o que você passa para o parâmetro de argumento de ITest<T> deve também derivar de ITest<T>.

Mesmo assim isso não impedirá todos casos do buraco.O código abaixo será compilado corretamente, porque A possui um construtor sem parâmetros.Mas como o construtor sem parâmetros de B é privado, a instanciação de B com seu processo falhará em tempo de execução.

public class A : ITest<A>
{
}

public class B : ITest<A>
{
  private B() { }
}

Outras dicas

Não quero ser muito direto, mas você entendeu mal o propósito das interfaces.

Uma interface significa que várias pessoas podem implementá-la em suas próprias classes e então passar instâncias dessas classes para outras classes serem usadas.A criação cria um acoplamento forte e desnecessário.

Parece que você realmente precisa de algum tipo de sistema de registro, seja para que as pessoas registrem instâncias de classes utilizáveis ​​que implementem a interface, ou de fábricas que possam criar esses itens mediante solicitação.

Juan,

Infelizmente não há como contornar isso em uma linguagem fortemente digitada.Você não poderá garantir em tempo de compilação que as classes poderão ser instanciadas pelo seu código baseado no ativador.

(ed.:removeu uma solução alternativa errada)

A razão é que, infelizmente, não é possível usar interfaces, classes abstratas ou métodos virtuais em combinação com construtores ou métodos estáticos.A breve razão é que os primeiros não contêm informações de tipo explícitas e os últimos exigem informações de tipo explícitas.

Construtores e métodos estáticos deve ter informações de tipo explícitas (ali mesmo no código) disponíveis no momento da chamada.Isso é necessário porque não há nenhuma instância da classe envolvida que possa ser consultada pelo tempo de execução para obter o tipo subjacente, que o tempo de execução precisa para determinar qual método concreto real chamar.

O objetivo de uma interface, classe abstrata ou método virtual é ser capaz de fazer uma chamada de função sem informações de tipo explícitas, e isso é possibilitado pelo fato de que há uma instância sendo referenciada, que possui informações de tipo "ocultas" não disponíveis diretamente para o código de chamada.Portanto, estes dois mecanismos são simplesmente mutuamente exclusivos.Eles não podem ser usados ​​juntos porque quando você os mistura, você acaba sem nenhuma informação concreta de tipo em lugar nenhum, o que significa que o tempo de execução não tem ideia de onde encontrar a função que você está pedindo para chamar.

Você pode usar restrição de parâmetro de tipo

interface ITest<T> where T: new()
{
    //...
}

class Test: ITest<Test>
{
    //...
}

Então você precisa de um coisa que pode criar instâncias de um tipo desconhecido que implementa uma interface.Você tem basicamente três opções:um objeto de fábrica, um objeto Type ou um delegado.Aqui estão os dados:

public interface IInterface
{
    void DoSomething();
}

public class Foo : IInterface
{
    public void DoSomething() { /* whatever */ }
}

Usar Type é muito feio, mas faz sentido em alguns cenários:

public IInterface CreateUsingType(Type thingThatCreates)
{
    ConstructorInfo constructor = thingThatCreates.GetConstructor(Type.EmptyTypes);
    return (IInterface)constructor.Invoke(new object[0]);
}

public void Test()
{
    IInterface thing = CreateUsingType(typeof(Foo));
}

O maior problema com isso é que em tempo de compilação, você não tem garantia de que Foo realmente tem um construtor padrão.Além disso, a reflexão é um pouco lenta se este for um código crítico para o desempenho.

A solução mais comum é usar uma fábrica:

public interface IFactory
{
    IInterface Create();
}

public class Factory<T> where T : IInterface, new()
{
    public IInterface Create() { return new T(); }
}

public IInterface CreateUsingFactory(IFactory factory)
{
    return factory.Create();
}

public void Test()
{
    IInterface thing = CreateUsingFactory(new Factory<Foo>());
}

Acima, IFactory é o que realmente importa.Factory é apenas uma aula de conveniência para aulas que fazer forneça um construtor padrão.Esta é a solução mais simples e muitas vezes a melhor.

A terceira solução atualmente incomum, mas que provavelmente se tornará mais comum, é usar um delegado:

public IInterface CreateUsingDelegate(Func<IInterface> createCallback)
{
    return createCallback();
}

public void Test()
{
    IInterface thing = CreateUsingDelegate(() => new Foo());
}

A vantagem aqui é que o código é curto e simples, pode funcionar com qualquer método de construção e (com fechamentos) permite transmitir facilmente dados adicionais necessários para construir os objetos.

Chame um método RegisterType com o tipo e restrinja-o usando genéricos.Então, em vez de percorrer assemblies para encontrar implementadores de ITest, apenas armazene-os e crie a partir daí.

void RegisterType<T>() where T:ITest, new() {
}

Eu não acho.

Você também não pode usar uma classe abstrata para isso.

Gostaria de lembrar a todos que:

  1. Escrever atributos em .NET é fácil
  2. É fácil escrever ferramentas de análise estática em .NET que garantam a conformidade com os padrões da empresa

Escrever uma ferramenta para capturar todas as classes concretas que implementam uma determinada interface/possuem um atributo e verificar se ela possui um construtor sem parâmetros leva cerca de 5 minutos de esforço de codificação.Você o adiciona à etapa de pós-construção e agora tem uma estrutura para quaisquer outras análises estáticas que precisar realizar.

A linguagem, o compilador, o IDE, seu cérebro – são todos ferramentas.Usa-os!

Não, você não pode fazer isso.Talvez para a sua situação uma interface de fábrica seja útil?Algo como:

interface FooFactory {
    Foo createInstance();
}

Para cada implementação do Foo você cria uma instância do FooFactory que sabe como criá-la.

Você não precisa de um construtor sem parâmetros para o Ativador instanciar sua classe.Você pode ter um construtor parametrizado e passar todos os parâmetros do ativador.Confira MSDN sobre isso.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top