property - interface properties c#
¿Interfaz que define una firma de constructor? (17)
Como ya se señaló, no puede tener constructores en una interfaz. Pero dado que este es un resultado altamente calificado en Google unos 7 años más tarde, pensé que estaría presente aquí, específicamente para mostrar cómo se puede usar una clase base abstracta junto con su interfaz existente y tal vez reducir la cantidad de refactorización Necesitamos en el futuro para situaciones similares. Este concepto ya ha sido insinuado en algunos de los comentarios, pero pensé que valdría la pena mostrar cómo hacerlo realmente.
Así que tienes tu interfaz principal que se ve así hasta ahora:
public interface IDrawable
{
void Update();
void Draw();
}
Ahora crea una clase abstracta con el constructor que quieres imponer. En realidad, ya que está disponible desde el momento en que escribió su pregunta original, podemos obtener un poco de fantasía aquí y usar genéricos en esta situación para poder adaptarla a otras interfaces que puedan necesitar la misma funcionalidad pero que tengan diferentes requisitos de constructor:
public abstract class MustInitialize<T>
{
public MustInitialize(T parameters)
{
}
}
Ahora deberá crear una nueva clase que herede tanto de la interfaz IDrawable como de la clase abstracta MustInitialize:
public class Drawable : MustInitialize<GraphicsDeviceManager>, IDrawable
{
GraphicsDeviceManager _graphicsDeviceManager;
public Drawable(GraphicsDeviceManager graphicsDeviceManager)
: base (graphicsDeviceManager)
{
_graphicsDeviceManager = graphicsDeviceManager;
}
public void Update()
{
//use _graphicsDeviceManager here to do whatever
}
public void Draw()
{
//use _graphicsDeviceManager here to do whatever
}
}
Luego simplemente crea una instancia de Drawable y listo:
IDrawable drawableService = new Drawable(myGraphicsDeviceManager);
Lo bueno aquí es que la nueva clase Drawable que creamos todavía se comporta como lo que esperaríamos de un IDrawable.
Si necesita pasar más de un parámetro al constructor MustInitialize, puede crear una clase que defina las propiedades de todos los campos que deberá pasar.
Es extraño que esta sea la primera vez que me he encontrado con este problema, pero:
¿Cómo definir un constructor en una interfaz de C #?
Editar
Algunas personas querían un ejemplo (es un proyecto de tiempo libre, así que sí, es un juego)
Extraíble
+ Actualización
+ Dibujar
Para poder actualizar (verificar el borde de la pantalla, etc.) y dibujarse, siempre necesitará un GraphicsDeviceManager
. Así que quiero asegurarme de que el objeto tenga una referencia a él. Esto pertenecería en el constructor.
Ahora que escribí esto, creo que lo que estoy implementando aquí es IObservable
y GraphicsDeviceManager
debería tomar el IDrawable
... Parece que, o bien no obtengo el marco XNA, o el marco no está muy bien pensado.
Editar
Parece haber cierta confusión sobre mi definición de constructor en el contexto de una interfaz. De hecho, una interfaz no puede ser instanciada por lo que no necesita un constructor. Lo que quería definir era una firma para un constructor. Exactamente como una interfaz puede definir una firma de un determinado método, la interfaz podría definir la firma de un constructor.
El enfoque genérico de fábrica todavía parece ideal. Usted sabría que la fábrica requiere un parámetro, y sucedería que esos parámetros se pasan al constructor del objeto que se está creando.
Tenga en cuenta que esto es solo un pseudo código verificado por sintaxis, puede haber una advertencia en tiempo de ejecución que me estoy perdiendo aquí:
public interface IDrawableFactory
{
TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager)
where TDrawable: class, IDrawable, new();
}
public class DrawableFactory : IDrawableFactory
{
public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager)
where TDrawable : class, IDrawable, new()
{
return (TDrawable) Activator
.CreateInstance(typeof(TDrawable),
graphicsDeviceManager);
}
}
public class Draw : IDrawable
{
//stub
}
public class Update : IDrawable {
private readonly GraphicsDeviceManager _graphicsDeviceManager;
public Update() { throw new NotImplementedException(); }
public Update(GraphicsDeviceManager graphicsDeviceManager)
{
_graphicsDeviceManager = graphicsDeviceManager;
}
}
public interface IDrawable
{
//stub
}
public class GraphicsDeviceManager
{
//stub
}
Un ejemplo de posible uso:
public void DoSomething()
{
var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
var myDrawObject = GetDrawingObject<Draw>(null);
}
Por supuesto, solo querría que las instancias creadas a través de la fábrica le garanticen que siempre tendrá un objeto debidamente inicializado. Tal vez el uso de un marco de inyección de dependencia como AutoFac tenga sentido; Update () podría "pedir" al contenedor IoC un nuevo objeto GraphicsDeviceManager.
El propósito de una interfaz es imponer una cierta firma de objeto. No debe preocuparse explícitamente por cómo funciona un objeto internamente. Por lo tanto, un constructor en una interfaz realmente no tiene sentido desde un punto de vista conceptual.
Sin embargo, hay algunas alternativas:
Cree una clase abstracta que actúe como una implementación predeterminada mínima. Esa clase debe tener los constructores que espera que tengan las clases de implementación.
Si no le importa el exceso, use el patrón AbstractFactory y declare un método en la interfaz de clase de fábrica que tenga las firmas requeridas.
Pase el
GraphicsDeviceManager
como un parámetro a los métodosUpdate
yDraw
.Use un marco de programación orientado a objetos de composición para pasar el
GraphicsDeviceManager
a la parte del objeto que lo requiere. Esta es una solución bastante experimental en mi opinión.
La situación que describe no es fácil de manejar en general. Un caso similar serían las entidades en una aplicación empresarial que requieren acceso a la base de datos.
No es posible crear una interfaz que defina constructores, pero sí es posible definir una interfaz que obligue a un tipo a tener un constructor sin parámetros, aunque sea una sintaxis muy fea que usa genéricos ... En realidad no estoy tan seguro de que Es realmente un buen patrón de codificación.
public interface IFoo<T> where T : new()
{
void SomeMethod();
}
public class Foo : IFoo<Foo>
{
// This will not compile
public Foo(int x)
{
}
#region ITest<Test> Members
public void SomeMethod()
{
throw new NotImplementedException();
}
#endregion
}
Por otro lado, si desea probar si un tipo tiene un constructor sin parámetros, puede hacerlo utilizando la reflexión:
public static class TypeHelper
{
public static bool HasParameterlessConstructor(Object o)
{
return HasParameterlessConstructor(o.GetType());
}
public static bool HasParameterlessConstructor(Type t)
{
// Usage: HasParameterlessConstructor(typeof(SomeType))
return t.GetConstructor(new Type[0]) != null;
}
}
Espero que esto ayude.
Podría hacer esto con el truco de los genéricos, pero aún es vulnerable a lo que Jon Skeet escribió:
public interface IHasDefaultConstructor<T> where T : IHasDefaultConstructor<T>, new()
{
}
La clase que implementa esta interfaz debe tener un constructor sin parámetros:
public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
public A(int a) { } //compile time error
}
Recordé esta pregunta y pensé: tal vez estamos abordando este problema de manera incorrecta. Las interfaces pueden no ser el camino a seguir cuando se trata de definir un constructor con ciertos parámetros ... pero una clase base (abstracta) es.
Si crea una clase base con un constructor allí que acepte los parámetros que necesita, todas las clases que la generen deben proporcionarlos.
public abstract class Foo
{
protected Foo(SomeParameter x)
{
this.X = x;
}
public SomeParameter X { get; private set }
}
public class Bar : Foo // Bar inherits from Foo
{
public Bar()
: base(new SomeParameter("etc...")) // Bar will need to supply the constructor param
{
}
}
Sería muy útil si fuera posible definir constructores en interfaces.
Dado que una interfaz es un contrato que debe ser utilizado de la manera especificada. El siguiente enfoque podría ser una alternativa viable para algunos escenarios:
public interface IFoo {
/// <summary>
/// Initialize foo.
/// </summary>
/// <remarks>
/// Classes that implement this interface must invoke this method from
/// each of their constructors.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown when instance has already been initialized.
/// </exception>
void Initialize(int a);
}
public class ConcreteFoo : IFoo {
private bool _init = false;
public int b;
// Obviously in this case a default value could be used for the
// constructor argument; using overloads for purpose of example
public ConcreteFoo() {
Initialize(42);
}
public ConcreteFoo(int a) {
Initialize(a);
}
public void Initialize(int a) {
if (_init)
throw new InvalidOperationException();
_init = true;
b = a;
}
}
Si bien no puede definir una firma de constructor en una interfaz, creo que vale la pena mencionar que este puede ser un lugar para considerar una clase abstracta. Las clases abstractas pueden definir firmas de métodos no implementados (abstractos) de la misma forma que una interfaz, pero también pueden haber implementado métodos (concretos) y constructores.
La desventaja es que, debido a que es un tipo de clase, no se puede usar para ninguno de los escenarios de tipo de herencia múltiple que puede usar una interfaz.
Si entendí OP correctamente, queremos hacer cumplir un contrato donde GraphicsDeviceManager siempre se inicializa mediante la implementación de clases. Tuve un problema similar y buscaba una solución mejor, pero esto es lo mejor que puedo pensar:
Agregue un SetGraphicsDeviceManager (GraphicsDeviceManager gdo) a la interfaz, y de esa manera las clases implementadoras se verán obligadas a escribir una lógica que requerirá una llamada del constructor.
Una contribución muy tardía que demuestra otro problema con los constructores interconectados. (Elijo esta pregunta porque tiene la articulación más clara del problema). Supongamos que pudiéramos tener:
interface IPerson
{
IPerson(string name);
}
interface ICustomer
{
ICustomer(DateTime registrationDate);
}
class Person : IPerson, ICustomer
{
Person(string name) { }
Person(DateTime registrationDate) { }
}
Cuando, por convención, la implementación del "constructor de interfaz" se sustituye por el nombre del tipo.
Ahora haz una instancia:
ICustomer a = new Person("Ernie");
¿ ICustomer
se ICustomer
el contrato con el ICustomer
?
Y que hay de esto:
interface ICustomer
{
ICustomer(string address);
}
Una forma de forzar a algún tipo de constructor es declarar solo a los Getters
en la interfaz, lo que podría significar que la clase implementadora debe tener un método, idealmente un constructor, para tener el valor establecido ( private
).
Una forma de resolver este problema es aprovechar los genéricos y la nueva restricción ().
En lugar de expresar su constructor como un método / función, puede expresarlo como una clase / interfaz de fábrica. Si especifica la nueva restricción genérica () en cada sitio de llamada que necesita crear un objeto de su clase, podrá pasar los argumentos del constructor en consecuencia.
Para su ejemplo de IDrawable:
public interface IDrawable
{
void Update();
void Draw();
}
public interface IDrawableConstructor<T> where T : IDrawable
{
T Construct(GraphicsDeviceManager manager);
}
public class Triangle : IDrawable
{
public GraphicsDeviceManager Manager { get; set; }
public void Draw() { ... }
public void Update() { ... }
public Triangle(GraphicsDeviceManager manager)
{
Manager = manager;
}
}
public TriangleConstructor : IDrawableConstructor<Triangle>
{
public Triangle Construct(GraphicsDeviceManager manager)
{
return new Triangle(manager);
}
}
Ahora cuando lo uses:
public void SomeMethod<TBuilder>(GraphicsDeviceManager manager)
where TBuilder: IDrawableConstructor<Triangle>, new()
{
// If we need to create a triangle
Triangle triangle = new TBuilder().Construct(manager);
// Do whatever with triangle
}
Incluso puede concentrar todos los métodos de creación en una sola clase usando una implementación de interfaz explícita:
public DrawableConstructor : IDrawableConstructor<Triangle>,
IDrawableConstructor<Square>,
IDrawableConstructor<Circle>
{
Triangle IDrawableConstructor<Triangle>.Construct(GraphicsDeviceManager manager)
{
return new Triangle(manager);
}
Square IDrawableConstructor<Square>.Construct(GraphicsDeviceManager manager)
{
return new Square(manager);
}
Circle IDrawableConstructor<Circle>.Construct(GraphicsDeviceManager manager)
{
return new Circle(manager);
}
}
Para usarlo:
public void SomeMethod<TBuilder, TShape>(GraphicsDeviceManager manager)
where TBuilder: IDrawableConstructor<TShape>, new()
{
// If we need to create an arbitrary shape
TShape shape = new TBuilder().Construct(manager);
// Do whatever with the shape
}
Otra forma es mediante el uso de expresiones lambda como inicializadores. En algún momento al principio de la jerarquía de llamadas, sabrá qué objetos necesitará instanciar (es decir, cuando está creando u obteniendo una referencia a su objeto GraphicsDeviceManager). Tan pronto como lo tengas, pasa la lambda.
() => new Triangle(manager)
a los métodos subsiguientes para que sepan cómo crear un triángulo a partir de entonces. Si no puede determinar todos los métodos posibles que necesitará, siempre puede crear un diccionario de tipos que implemente IDrawable utilizando la reflexión y registrar la expresión lambda que se muestra arriba en un diccionario que puede almacenar en una ubicación compartida o pasar a Más funciones de llamadas.
Una forma de resolver este problema que encontré es separar la construcción en una fábrica separada. Por ejemplo, tengo una clase abstracta llamada IQueueItem, y necesito una forma de traducir ese objeto hacia y desde otro objeto (CloudQueueMessage). Así que en la interfaz IQueueItem tengo -
public interface IQueueItem
{
CloudQueueMessage ToMessage();
}
Ahora, también necesito una forma para que mi clase de cola real traduzca un CloudQueueMessage de nuevo a un IQueueItem, es decir, la necesidad de una construcción estática como IQueueItem objMessage = ItemType.FromMessage. En su lugar, definí otra interfaz IQueueFactory -
public interface IQueueItemFactory<T> where T : IQueueItem
{
T FromMessage(CloudQueueMessage objMessage);
}
Ahora finalmente puedo escribir mi clase de cola genérica sin la nueva restricción () que en mi caso era el problema principal.
public class AzureQueue<T> where T : IQueueItem
{
private IQueueItemFactory<T> _objFactory;
public AzureQueue(IQueueItemFactory<T> objItemFactory)
{
_objFactory = objItemFactory;
}
public T GetNextItem(TimeSpan tsLease)
{
CloudQueueMessage objQueueMessage = _objQueue.GetMessage(tsLease);
T objItem = _objFactory.FromMessage(objQueueMessage);
return objItem;
}
}
Ahora puedo crear una instancia que satisfaga los criterios para mí.
AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())
Esperemos que esto ayude a alguien más algún día, obviamente, se eliminó una gran cantidad de código interno para intentar mostrar el problema y la solución.
Usted no puede De vez en cuando es un dolor, pero de todos modos no podrías llamarlo usando técnicas normales.
En una publicación de blog he sugerido interfaces estáticas que solo serían utilizables en restricciones de tipo genérico, pero podrían ser realmente útiles, IMO.
Un punto sobre si podría definir un constructor dentro de una interfaz, tendría problemas para derivar clases:
public class Foo : IParameterlessConstructor
{
public Foo() // As per the interface
{
}
}
public class Bar : Foo
{
// Yikes! We now don''t have a parameterless constructor...
public Bar(int x)
{
}
}
Usted no puede
Las interfaces definen los contratos que implementan otros objetos y, por lo tanto, no tienen un estado que deba inicializarse.
Si tiene algún estado que necesita inicializarse, debe considerar usar una clase base abstracta en su lugar.
tu no
el constructor es parte de la clase que puede implementar una interfaz. La interfaz es solo un contrato de métodos que la clase debe implementar.
Las interfaces son contractuales. La interfaz no permite campos, es decir, no es necesario inicializar nada. La interfaz solo contiene referencia. La interfaz debe requerir una clase derivada para mantener la memoria.