c# - usan - ¿Cómo puede requerir un constructor sin parámetros para los tipos que implementan una interfaz?
porque usar interfaces c# (10)
Juan,
Desafortunadamente no hay forma de evitar esto en un lenguaje fuertemente tipado. No podrá garantizar en tiempo de compilación que las clases podrán ser instanciadas por su código basado en Activador.
(ed: eliminó una solución alternativa errónea)
La razón es que, desafortunadamente, no es posible usar 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.
Los constructores y los métodos estáticos deben tener información de tipo explícita (en el código) disponible en el momento de la llamada. Esto es necesario porque no hay ninguna instancia de la clase involucrada que pueda ser consultada por el tiempo de ejecución para obtener el tipo subyacente, que el tiempo de ejecución necesita para determinar qué método concreto real llamar.
El punto completo de una interfaz, clase abstracta o método virtual es poder realizar una llamada de función sin información de tipo explícita, y esto está habilitado por el hecho de que se hace referencia a una instancia, que tiene información de tipo "oculto" no directamente disponible para el código de llamada. Entonces estos dos mecanismos son simplemente mutuamente exclusivos. No se pueden usar juntos porque cuando los mezclas, terminas sin ningún tipo de información concreta en ninguna parte, lo que significa que el tiempo de ejecución no tiene idea de dónde encontrar la función a la que le pides que llame.
¿Hay alguna manera?
Necesito todos los tipos que implementan una interfaz específica para tener 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 (por ejemplo, la interfaz).
La interfaz será interna para el ensamblaje
Si tiene una sugerencia para este escenario sin interfaces, con mucho gusto lo tendré en cuenta ...
Me gustaría recordarles a todos que:
- Escribir atributos en .NET es fácil
- Escribir herramientas de análisis estáticos en .NET que garanticen la conformidad con los estándares de la compañía es fácil
Escribir una herramienta para captar todas las clases concretas que implementan una determinada interfaz / tiene un atributo y verificar que tiene un constructor sin parámetros lleva 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: todas son herramientas. ¡Usalos, usalos a ellos!
No debe ser demasiado directo, pero ha entendido mal 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 ser utilizadas. La creación crea un acoplamiento fuerte innecesario.
Parece que realmente se 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 pueden crear dichos elementos a pedido.
No lo creo.
Tampoco puedes usar una clase abstracta para esto.
No necesita un constructor sin parámetros para que el Activador pueda instanciar su clase. Puede tener un constructor parametrizado y pasar todos los parámetros desde el Activador. Echa un vistazo a MSDN en esto .
No, no puedes hacer eso. ¿Tal vez para su situación una interfaz de fábrica sería útil? Algo como:
interface FooFactory {
Foo createInstance();
}
Para cada implementación de Foo crea una instancia de FooFactory que sepa cómo crearla.
Puede usar la restricción de parámetro de tipo
interface ITest<T> where T: new()
{
//...
}
class Test: ITest<Test>
{
//...
}
Llame a un método RegisterType con el tipo y constrúyalo utilizando genéricos. Luego, en lugar de caminar ensamblajes para encontrar implementadores de ITest, simplemente guárdelos y cree desde allí.
void RegisterType<T>() where T:ITest, new() {
}
Entonces necesitas algo que pueda crear instancias de un tipo desconocido que implemente una interfaz. Básicamente tiene tres opciones: un objeto de fábrica, un objeto Type 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 tiempo de compilación, no tienes garantía de que Foo tenga un constructor predeterminado. Además, la reflexión es un poco lenta si se trata de un código de rendimiento crítico.
La solución más común es usar 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 las clases que proporcionan un constructor predeterminado. Esta es la solución más simple y a menudo mejor.
La tercera solución actualmente poco común pero probable de convertirse en más común es usar 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 breve y simple, puede funcionar con cualquier método de construcción y (con cierres) le permite transferir fácilmente datos adicionales necesarios para construir los objetos.
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 "engañar" y enviar información de tipo junto con la interfaz. Lo más importante que hay que recordar aquí es que la restricción no está en la interfaz con la que estás trabajando directamente. No es una restricción en la interfaz en sí, sino en algún otro tipo que "circule" en 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 compilaría bien pero fallaría 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. De nuevo, la restricción está en T y, por lo tanto, en String, en lugar de en ITest o Something. Como se cumple la restricción sobre T, se compilará. Pero fallará en tiempo de ejecución.
Para evitar algunas instancias de este problema, debe 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: ITest <T>. Esta restricción especifica que lo que pasa al parámetro de argumento de ITest <T> también debe derivarse de ITest <T>.
Aun así, esto no evitará todos los casos del agujero. El siguiente código compilará bien, porque A tiene un constructor sin parámetros. Pero dado que el constructor sin parámetros de B es privado, la instancia de B con su proceso fallará en el tiempo de ejecución.
public class A : ITest<A>
{
}
public class B : ITest<A>
{
private B() { }
}