Pregunta

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

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

ID extraíble
 + Actualizar
 + 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 al 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 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.

¿Fue útil?

Solución

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 podría usar una clase base abstracta en tándem con su interfaz existente y tal vez reducir la cantidad de refactorización Necesitamos en el futuro para situaciones similares. Este concepto ya se ha aludido 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 cree una clase abstracta con el constructor que desea imponer. En realidad, dado 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 poder adaptarla a otras interfaces que puedan necesitar la misma funcionalidad pero que tengan diferentes requisitos de construcción:

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 ID extraíble.

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.

Otros consejos

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

En una publicación de blog, sugerí 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 las 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)
    {
    }
}

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

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

Ahora crea una instancia:

ICustomer a = new Person("Ernie");

¿Diríamos que el contrato ICustomer se cumple?

Y qué hay de esto:

interface ICustomer
{
    ICustomer(string address);
}

No puedes.

Las interfaces definen los contratos que implementan otros objetos y, por lo tanto, no tienen un estado que deba inicializarse.

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

No es posible crear una interfaz que defina constructores, pero es posible definir una interfaz que obligue a un tipo a tener un constructor sin parámetros, aunque sea una sintaxis muy fea que utiliza genéricos. ... Realmente 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 otra parte, 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.

Estaba mirando hacia atrás en 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, cada clase que se derive de ella debe 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
  {
  }
}

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())

espero 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

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.

Nota, esto es solo un pseudo código verificado por sintaxis, puede haber una advertencia en el 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);
    }

Por supuesto, solo querría que las instancias creadas a través de la fábrica le garanticen que siempre tendrá un objeto correctamente inicializado. Tal vez tenga sentido utilizar un marco de inyección de dependencias como AutoFac ; Actualizar () podría " preguntar " el contenedor IoC para un nuevo objeto GraphicsDeviceManager.

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 ese momento. Si no puede determinar todos los métodos posibles que necesitará, siempre puede crear un diccionario de tipos que implemente IDrawable mediante 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 llamadas a funciones adicionales.

Puedes 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
}

El propósito de una interfaz es imponer una determinada 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 debería tener los constructores que esperas implementando clases. tener.

  • Si no le importa la exageración, use el patrón AbstractFactory y declarar un método en la interfaz de clase de fábrica que tenga el requerido firmas.

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

  • Use un marco de programación orientado a objetos compositivos 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.

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.

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

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

}

Una forma de forzar algún tipo de constructor es declarar solo 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 (< código> privado ly) para ello.

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 manera que una interfaz, pero también pueden haber implementado métodos y constructores (concretos).

El inconveniente 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.

Uso el siguiente patrón para hacerlo a prueba de balas.

  • Un desarrollador que deriva su clase de la base no puede crear accidentalmente un constructor de acceso público
  • Los desarrolladores de la clase final están obligados a pasar por el método de creación común
  • Todo es seguro para el tipo, no se requieren piezas fundidas
  • Es 100% flexible y puede reutilizarse en cualquier lugar, donde puede definir su propia base. clase.
  • Pruébelo, no puede romperlo sin hacer modificaciones a las clases base (excepto si define un indicador obsoleto sin el indicador de error establecido en verdadero, pero incluso entonces termina con una advertencia)

        public abstract class Base<TSelf, TParameter>
        where TSelf : Base<TSelf, TParameter>, new()
    {
        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TSelf Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);
    
            return me;
        }
    
        [Obsolete(FactoryMessage, true)]
        protected Base()
        {
        }
    
    
    
        protected virtual void Initialize(TParameter parameter)
        {
    
        }
    }
    
    public abstract class BaseWithConfig<TSelf, TConfig>: Base<TSelf, TConfig>
        where TSelf : BaseWithConfig<TSelf, TConfig>, new()
    {
        public TConfig Config { get; private set; }
    
        [Obsolete(FactoryMessage, true)]
        protected BaseWithConfig()
        {
        }
        protected override void Initialize(TConfig parameter)
        {
            this.Config = parameter;
        }
    }
    
    public class MyService : BaseWithConfig<MyService, (string UserName, string Password)>
    {
        [Obsolete(FactoryMessage, true)]
        public MyService()
        {
        }
    }
    
    public class Person : Base<Person, (string FirstName, string LastName)>
    {
        [Obsolete(FactoryMessage,true)]
        public Person()
        {
        }
    
        protected override void Initialize((string FirstName, string LastName) parameter)
        {
            this.FirstName = parameter.FirstName;
            this.LastName = parameter.LastName;
        }
    
        public string LastName { get; private set; }
    
        public string FirstName { get; private set; }
    }
    
    
    
    [Test]
    public void FactoryTest()
    {
        var notInitilaizedPerson = new Person(); // doesn't compile because of the obsolete attribute.
        Person max = Person.Create(("Max", "Mustermann"));
        Assert.AreEqual("Max",max.FirstName);
    
        var service = MyService.Create(("MyUser", "MyPassword"));
        Assert.AreEqual("MyUser", service.Config.UserName);
    }
    

EDITAR: Y aquí hay un ejemplo basado en su ejemplo de dibujo que incluso impone la abstracción de la interfaz

        public abstract class BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithAbstraction<TSelf, TInterface, TParameter>, TInterface, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected BaseWithAbstraction()
        {
        }

        protected const string FactoryMessage = "Use YourClass.Create(...) instead";
        public static TInterface Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);

            return me;
        }

        protected virtual void Initialize(TParameter parameter)
        {

        }
    }



    public abstract class BaseWithParameter<TSelf, TInterface, TParameter> : BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithParameter<TSelf, TInterface, TParameter>, TInterface, new()
    {
        protected TParameter Parameter { get; private set; }

        [Obsolete(FactoryMessage, true)]
        protected BaseWithParameter()
        {
        }
        protected sealed override void Initialize(TParameter parameter)
        {
            this.Parameter = parameter;
            this.OnAfterInitialize(parameter);
        }

        protected virtual void OnAfterInitialize(TParameter parameter)
        {
        }
    }


    public class GraphicsDeviceManager
    {

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

    internal abstract class Drawable<TSelf> : BaseWithParameter<TSelf, IDrawable, GraphicsDeviceManager>, IDrawable 
        where TSelf : Drawable<TSelf>, IDrawable, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected Drawable()
        {
        }

        public abstract void Update();
        public abstract void Draw();
    }

    internal class Rectangle : Drawable<Rectangle>
    {
        [Obsolete(FactoryMessage, true)]
        public Rectangle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }
    internal class Circle : Drawable<Circle>
    {
        [Obsolete(FactoryMessage, true)]
        public Circle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }


    [Test]
    public void FactoryTest()
    {
        // doesn't compile because interface abstraction is enforced.
        Rectangle rectangle = Rectangle.Create(new GraphicsDeviceManager());

        // you get only the IDrawable returned.
        IDrawable service = Circle.Create(new GraphicsDeviceManager());
    }

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 estaba buscando una mejor solución, pero esta es la mejor en la 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.

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