Interfaz definiendo una firma de constructor?


Es raro que esta sea la primera vez que me he encontrado con este problema, pero:

¿Cómo se define 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)

IDrawable
+ Update
+Sorteo

Para poder actualizar (comprobar el borde de la pantalla, etc.) y dibujarse siempre necesitará un GraphicsDeviceManager. Así que quiero asegurarme de que el objeto tiene una referencia a él. Esto pertenecería a la constructor.

Ahora que escribí esto creo que lo que estoy implementando aquí es IObservable y el GraphicsDeviceManager debería tomar el IDrawable... Parece que o no entiendo el marco XNA, o el marco no está muy bien pensado.

Editar
Parece haber cierta confusión acerca de 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.

Author: Adi Lester, 2009-03-06

16 answers

Como ya se ha señalado, no puede tener constructores en una Interfaz. Pero ya que este es un resultado tan altamente clasificado en Google unos 7 años más tarde, pensé que podría chip aquí - específicamente para mostrar cómo se podría utilizar una clase base abstracta en conjunto con su Interfaz existente y tal vez reducir la cantidad de refactorización necesaria en el futuro para situaciones similares. Este concepto ya se ha insinuado en algunos de los comentarios, pero pensé que valdría la pena mostrar cómo en realidad hacerlo.

Así que tienes tu interfaz principal que se ve así hasta ahora:

public interface IDrawable
{
    void Update();
    void Draw();
}

Ahora cree una clase abstracta con el constructor que desea aplicar. En realidad, ya que ahora 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 que podamos adaptar esto a otras interfaces que podrían necesitar la misma funcionalidad pero tienen diferentes requisitos de constructor:

public abstract class MustInitialize<T>
{
    public MustInitialize(T parameters)
    {

    }
}

Ahora necesitarás cree una nueva clase que hereda 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 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 propiedades para todos los campos que necesito pasar.

 81
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-16 21:57:06

No puedes. Ocasionalmente es un dolor, pero no podrías llamarlo usando técnicas normales de todos modos.

En una entrada 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 acerca de si pudiera 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)
    {
    }
}
 298
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-17 13:53:18

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 podríamos tener:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

class Person : IPerson, ICustomer
{
    Person(string name) { }
    Person(DateTime registrationDate) { }
}

Donde por convención la implementación del "constructor de interfaz" se sustituye por el nombre del tipo.

Ahora crea una instancia:

ICustomer a = new Person("Ernie");

¿Diríamos que el contrato ICustomer es obedecido?

Y qué hay de esto:

interface ICustomer
{
    ICustomer(string address);
}
 131
Author: Gert Arnold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-04 10:07:25

No puedes.

Las interfaces definen contratos que otros objetos implementan y, por lo tanto, no tienen ningún estado que necesite inicializarse.

Si tiene algún estado que necesita inicializarse, debería considerar usar una clase base abstracta en su lugar.

 56
Author: Michael,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-06 18:15:32

No es posible crear una interfaz que defina constructores, pero es posible definir una interfaz que obliga 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 sea 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 quieres probar si un tipo tiene un constructor sin parámetros, puedes hacerlo usando 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.

 19
Author: Jeroen Landheer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-06 18:40:04

Estaba mirando hacia atrás en esta pregunta y pensé para mí mismo, tal vez estamos abordando este problema de la manera equivocada. 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) lo es.

Si crea una clase base con un constructor allí que acepta los parámetros que necesita, cada clase que derrive de ella necesita suministrarlos.

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
  {
  }
}
 19
Author: Jeroen Landheer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-02 18:35:26

El enfoque genérico de fábrica todavía parece ideal. Usted sabría que la fábrica requiere un parámetro, y simplemente sucedería que esos parámetros se pasan a lo largo del constructor del objeto que se instancian.

Nota, esto es solo pseudo código verificado por sintaxis, puede haber una advertencia en tiempo de ejecución que me falta 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);
    }

Concedido, solo desea que las instancias de create a través de la fábrica para garantizar que siempre tiene un objeto inicializado apropiadamente. Quizás usar un framework de inyección de dependencias como AutoFac tendría sentido; Update() podría "pedir" al contenedor IoC un nuevo objeto GraphicsDeviceManager.

 5
Author: Matthew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-28 21:17:33

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 a un IQueueItem-es decir, la necesidad de una construcción estática como IQueueItem objMessage = ItemType.Desde el mensaje. 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 restricción new() que en mi caso fue 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 una gran cantidad de código interno eliminado para tratar de mostrar el problema y la solución

 5
Author: JTtheGeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-29 03:43:25

Una forma de resolver este problema es aprovechar los genéricos y la restricción new ().

En lugar de expresar su constructor como un método/función, puede expresarlo como una clase/interfaz de fábrica. Si especifica la restricción genérica new () en cada sitio de llamadas que necesita crear un objeto de su clase, podrá pasar argumentos de constructor en consecuencia.

Para su ejemplo 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 usas:

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 puedes concentrarte todos los métodos de creación en una sola clase usando 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 usando expresiones lambda como inicializadores. En algún momento temprano en la jerarquía de llamadas, sabrá qué objetos necesitará instanciar (es decir, cuando esté creando o obteniendo una referencia a su objeto GraphicsDeviceManager). Tan pronto como lo tenga, pase la lambda

() => new Triangle(manager) 

A métodos posteriores 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 implementen IDrawable usando reflexión y registrar la expresión lambda mostrada anteriormente en un diccionario que puede almacenar en una ubicación compartida o pasar a otras llamadas a funciones.

 3
Author: Cesar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-19 20:46:46

Usted podría hacer esto con truco genérico, pero todavía 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
}
 2
Author: ghord,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-14 07:35:24

No lo haces.

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.

 0
Author: royatl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-06 18:20:53

Sería muy útil si fuera posible definir constructores de interfaces.

Dado que una interfaz es un contrato que debe utilizarse 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;
    }

}
 0
Author: Lea Hayes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-18 01:15:23

Una forma de forzar algún tipo de constructor es declarar solo Getters en interface, lo que podría significar que la clase implementadora debe tener un método, idealmente un constructor, para tener el valor establecido (privately) para él.

 0
Author: Kunal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-12 16:27:45

Si bien no se 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 implementadas (abstractas) de la misma manera que una interfaz, pero también pueden tener implementados métodos y constructores (concretos).

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 una interfaz puede.

 0
Author: IrishLagger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-06 19:29:59

El propósito de una interfaz es imponer una firma de objeto determinada. Explícitamente no debe preocuparse 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 implementar clases a tener.

  • Si no le importa el exceso, utilice el patrón AbstractFactory y declarar un método en la interfaz de clase de fábrica que tiene el firma.

  • Pase el GraphicsDeviceManager como parámetro a los métodos Update y Draw.

  • Utilice 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 describir no es fácil de manejar en general. Un caso similar serían las entidades en una aplicación comercial que requieren acceso a la base de datos.

 0
Author: MauganRa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-18 18:31:50

Si entendí OP correctamente, queremos hacer cumplir un contrato donde GraphicsDeviceManager siempre se inicializa mediante la implementación de clases. Tenía un problema similar y estaba buscando una solución mejor, pero esto es lo mejor que se me ocurre:

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.

 -2
Author: Kabali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-20 18:58:22