¿Cómo se puede requerir un constructor sin parámetros para los tipos que implementan una interfaz?

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

Pregunta

¿Hay alguna manera?

Necesito que todos los tipos que implementan una interfaz específica tengan un constructor sin parámetros, ¿se puede hacer?

Estoy desarrollando el código base para que otros desarrolladores de mi empresa lo utilicen en un proyecto específico.

Hay un proceso que creará instancias de tipos (en diferentes subprocesos) que realizan ciertas tareas, y necesito que esos tipos sigan un contrato específico (ergo, la interfaz).

La interfaz será interna al ensamblaje.

Si tienes alguna sugerencia para este escenario sin interfaces, con gusto la tomaré en consideración...

¿Fue útil?

Solución

JuanManuel dijo:

esa es una de las razones por las que no entiendo por qué no puede ser parte del contrato en la interfaz

Es un mecanismo indirecto.El genérico le permite "hacer trampa" y enviar información de tipo junto con la interfaz.Lo fundamental que debe recordar aquí es que la restricción no está en la interfaz con la que está trabajando directamente.No es una restricción en la interfaz en sí, sino en algún otro tipo que "acompañará" la interfaz.Esta es la mejor explicación que puedo ofrecer, me temo.

A modo de ilustración de este hecho, señalaré un agujero que he notado en el código de aku.Es posible escribir una clase que se compile bien pero que falle en tiempo de ejecución cuando intentas crear una instancia:

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

Algo se deriva de ITest<T>, pero no implementa ningún constructor sin parámetros.Se compilará bien, porque String implementa un constructor sin parámetros.Nuevamente, la restricción está en T y, por lo tanto, en String, en lugar de ITest o Something.Dado que se cumple la restricción de T, esto se compilará.Pero fallará en tiempo de ejecución.

Para prevenir alguno En casos de este problema, es necesario agregar otra restricción a T, como se muestra a continuación:

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

Tenga en cuenta la nueva restricción:T:Prueba<T>.Esta restricción especifica que lo que se pasa al parámetro de argumento de ITest<T> debe también derivar desde ITest<T>.

Aún así esto no impedirá todo casos del agujero.El siguiente código se compilará bien, porque A tiene un constructor sin parámetros.Pero dado que el constructor sin parámetros de B es privado, la creación de una instancia de B con su proceso fallará en tiempo de ejecución.

public class A : ITest<A>
{
}

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

Otros consejos

No quiero ser demasiado directo, pero no has entendido bien el propósito de las interfaces.

Una interfaz significa que varias personas pueden implementarla en sus propias clases y luego pasar instancias de esas clases a otras clases para que las utilicen.La creación crea un acoplamiento fuerte e innecesario.

Parece que realmente necesita algún tipo de sistema de registro, ya sea para que las personas registren instancias de clases utilizables que implementan la interfaz o de fábricas que puedan crear dichos elementos a pedido.

Juan,

Lamentablemente, no hay forma de solucionar este problema en un lenguaje fuertemente tipado.No podrá garantizar en el momento de la compilación que el código basado en Activator podrá crear instancias de las clases.

(ed:eliminó una solución alternativa errónea)

La razón es que, desafortunadamente, no es posible utilizar interfaces, clases abstractas o métodos virtuales en combinación con constructores o métodos estáticos.La razón breve es que los primeros no contienen información de tipo explícita y los segundos requieren información de tipo explícita.

Constructores y métodos estáticos. debe tener información de tipo explícita (justo allí en el código) disponible en el momento de la llamada.Esto es necesario porque no hay ninguna instancia de la clase involucrada que el tiempo de ejecución pueda consultar para obtener el tipo subyacente, que el tiempo de ejecución necesita para determinar qué método concreto real llamar.

El objetivo de una interfaz, clase abstracta o método virtual es poder realizar una llamada a una función. sin información de tipo explícita, y esto se habilita por el hecho de que hay una instancia a la que se hace referencia, que tiene información de tipo "oculta" que no está directamente disponible para el código de llamada.Así pues, estos dos mecanismos son sencillamente mutuamente excluyentes.No se pueden usar juntos porque cuando los mezclas, terminas sin información de tipo concreto en ninguna parte, lo que significa que el tiempo de ejecución no tiene idea de dónde encontrar la función que le estás pidiendo que llame.

Puedes usar restricción de parámetro de tipo

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

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

Entonces necesitas un cosa que puede crear instancias de un tipo desconocido que implementa una interfaz.Tienes básicamente tres opciones:un objeto de fábrica, un objeto de tipo o un delegado.Aquí están los datos:

public interface IInterface
{
    void DoSomething();
}

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

Usar Type es bastante feo, pero tiene sentido en algunos escenarios:

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));
}

El mayor problema con esto es que en el momento de la compilación, no tienes garantía de que Foo realmente tiene un constructor predeterminado.Además, la reflexión es un poco lenta si se trata de un código crítico para el rendimiento.

La solución más común es utilizar una 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>());
}

En lo anterior, IFactory es lo que realmente importa.Factory es solo una clase de conveniencia para clases que hacer Proporcionar un constructor predeterminado.Ésta es la solución más sencilla y, a menudo, la mejor.

La tercera solución actualmente poco común pero que probablemente se vuelva más común es utilizar un delegado:

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

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

La ventaja aquí es que el código es corto y simple, puede funcionar con cualquier método de construcción y (con cierres) le permite pasar fácilmente datos adicionales necesarios para construir los objetos.

Llame a un método RegisterType con el tipo y limítelo mediante genéricos.Luego, en lugar de recorrer los ensamblajes para encontrar implementadores de ITest, simplemente guárdelos y cree desde allí.

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

No me parece.

Tampoco puedes usar una clase abstracta para esto.

Me gustaría recordarles a todos que:

  1. Escribir atributos en .NET es fácil
  2. Es fácil escribir herramientas de análisis estático en .NET que garanticen el cumplimiento de los estándares de la empresa.

Escribir una herramienta para capturar todas las clases concretas que implementan una determinada interfaz/tienen un atributo y verificar que tiene un constructor sin parámetros requiere aproximadamente 5 minutos de esfuerzo de codificación.Lo agrega a su paso posterior a la compilación y ahora tiene un marco para cualquier otro análisis estático que necesite realizar.

El lenguaje, el compilador, el IDE, tu cerebro: todos son herramientas.¡Usalos, usalos a ellos!

No, no puedes hacer eso.¿Quizás para su situación sería útil una interfaz de fábrica?Algo como:

interface FooFactory {
    Foo createInstance();
}

Para cada implementación de Foo, crea una instancia de FooFactory que sabe cómo crearla.

No necesita un constructor sin parámetros para que el Activador cree una instancia de su clase.Puede tener un constructor parametrizado y pasar todos los parámetros desde el Activador.Verificar MSDN sobre esto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top