Domanda

È strano che questa sia la prima volta che mi imbatto in questo problema, ma:

Come si definisce un costruttore in un'interfaccia C #?

Modifica
Alcune persone volevano un esempio (è un progetto per il tempo libero, quindi sì, è un gioco)

IDrawable
 + Aggiornamento
 + Disegna

Per poter aggiornare (controllare il bordo dello schermo ecc.) e disegnare se stesso sarà sempre necessario un GraphicsDeviceManager . Quindi voglio assicurarmi che l'oggetto abbia un riferimento ad esso. Questo appartiene al costruttore.

Ora che ho scritto questo, penso che ciò che sto implementando qui sia IObservable e il GraphicsDeviceManager dovrebbe prendere il IDrawable ... Sembra che non capisco il framework XNA, o il framework non è pensato molto bene.

Modifica
Sembra esserci un po 'di confusione sulla mia definizione di costruttore nel contesto di un'interfaccia. In effetti, non è possibile creare un'istanza di un'interfaccia, quindi non è necessario un costruttore. Quello che volevo definire era una firma per un costruttore. Proprio come un'interfaccia può definire una firma di un determinato metodo, l'interfaccia potrebbe definire la firma di un costruttore.

È stato utile?

Soluzione

Come già notato, non è possibile avere costruttori su un'interfaccia. Ma dato che questo è un risultato di così alto livello in Google circa 7 anni dopo, ho pensato di inserirmi qui - in particolare per mostrare come potresti usare una classe base astratta in tandem con la tua interfaccia esistente e forse ridurre la quantità di refactoring necessario in futuro per situazioni simili. Questo concetto è già stato accennato in alcuni dei commenti, ma ho pensato che varrebbe la pena mostrare come farlo davvero.

Quindi hai la tua interfaccia principale che assomiglia a questo finora:

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

Ora crea una classe astratta con il costruttore che desideri applicare. In realtà, dal momento che è ora disponibile dal momento in cui hai scritto la tua domanda originale, possiamo avere un po 'di fantasia qui e usare i generici in questa situazione in modo da poterlo adattare ad altre interfacce che potrebbero aver bisogno della stessa funzionalità ma con requisiti costruttori diversi:

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

    }
}

Ora dovrai creare una nuova classe che eredita sia dall'interfaccia IDrawable sia dalla classe astratta 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
    }
}

Quindi basta creare un'istanza di Drawable e sei a posto:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

La cosa interessante qui è che la nuova classe Drawable che abbiamo creato si comporta esattamente come quello che ci aspetteremmo da un IDrawable.

Se è necessario passare più di un parametro al costruttore MustInitialize, è possibile creare una classe che definisce le proprietà per tutti i campi che è necessario passare.

Altri suggerimenti

Non puoi. Occasionalmente è un dolore, ma non saresti in grado di chiamarlo usando le normali tecniche comunque.

In un post sul blog ho suggerito interfacce statiche che sarebbero utilizzabili solo con vincoli di tipo generico - ma potrebbero essere davvero utili, IMO.

Un punto su se potresti definire un costruttore all'interno di un'interfaccia, avresti problemi a derivare le classi:

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

Un contributo molto recente che dimostra un altro problema con i costruttori interfacciati. (Ho scelto questa domanda perché ha la più chiara articolazione del problema). Supponiamo che potremmo avere:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Dove per convenzione l'implementazione del "costruttore di interfacce" è sostituito dal nome del tipo.

Ora crea un'istanza:

ICustomer a = new Person("Ernie");

Diremmo che il contratto ICustomer è stato rispettato?

E che dire di questo:

interface ICustomer
{
    ICustomer(string address);
}

Non puoi.

Le interfacce definiscono i contratti che altri oggetti implementano e quindi non hanno uno stato che deve essere inizializzato.

Se si dispone di uno stato che deve essere inizializzato, è consigliabile utilizzare invece una classe base astratta.

Non è possibile creare un'interfaccia che definisce i costruttori, ma è possibile definire un'interfaccia che costringe un tipo ad avere un costruttore senza parametri, sebbene sia una brutta sintassi che usa generici ... In realtà non sono così sicuro che sia davvero un buon modello di codifica.

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
}

D'altra parte, se vuoi testare se un tipo ha un costruttore senza parametri, puoi farlo usando reflection:

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

Spero che questo aiuti.

Stavo guardando indietro a questa domanda e ho pensato a me stesso, forse stiamo affrontando questo problema nel modo sbagliato. Le interfacce potrebbero non essere la strada da percorrere quando si tratta di definire un costruttore con determinati parametri ... ma lo è una classe di base (astratta).

Se crei una classe base con un costruttore che accetta i parametri di cui hai bisogno, ogni classe che ne derivi deve fornirli.

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

Un modo per risolvere questo problema che ho riscontrato è separare la costruzione in una fabbrica separata. Ad esempio ho una classe astratta chiamata IQueueItem e ho bisogno di un modo per tradurre quell'oggetto da e verso un altro oggetto (CloudQueueMessage). Quindi sull'interfaccia IQueueItem ho -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

Ora, ho anche bisogno di un modo per la mia attuale classe di coda per tradurre un CloudQueueMessage in un IQueueItem, ovvero la necessità di una costruzione statica come IQueueItem objMessage = ItemType.FromMessage. Invece ho definito un'altra interfaccia IQueueFactory -

public interface IQueueItemFactory<T> where T : IQueueItem
{
    T FromMessage(CloudQueueMessage objMessage);
}

Ora posso finalmente scrivere la mia classe di coda generica senza il nuovo vincolo () che nel mio caso era il problema principale.

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

ora posso creare un'istanza che soddisfa i criteri per me

 AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())

speriamo che questo aiuti qualcun altro un giorno, ovviamente un sacco di codice interno rimosso per provare a mostrare il problema e la soluzione

L'approccio di fabbrica generico sembra ancora ideale. Sapresti che la factory richiede un parametro, e accadrebbe che i parametri vengano passati al costruttore dell'oggetto da istanziare.

Nota, questo è solo uno pseudo codice verificato dalla sintassi, potrebbe esserci un avvertimento di runtime che mi manca qui:

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 esempio di possibile utilizzo:

    public void DoSomething()
    {
        var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
        var myDrawObject = GetDrawingObject<Draw>(null);
    }

Concesso, vorresti solo che le istanze create tramite la fabbrica garantissero di avere sempre un oggetto inizializzato in modo appropriato. Forse avrebbe senso usare un framework di iniezione di dipendenza come AutoFac ; Update () potrebbe " chiedere " il contenitore IoC per un nuovo oggetto GraphicsDeviceManager.

Un modo per risolvere questo problema è sfruttare la generica e il nuovo vincolo ().

Invece di esprimere il costruttore come metodo / funzione, puoi esprimerlo come classe / interfaccia di fabbrica. Se specifichi il nuovo () vincolo generico su ogni sito di chiamata che deve creare un oggetto della tua classe, sarai in grado di passare gli argomenti del costruttore di conseguenza.

Per il tuo esempio 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);
    } 
}

Ora quando lo usi:

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
}

Puoi persino concentrare tutti i metodi di creazione in una singola classe usando l'implementazione esplicita dell'interfaccia:

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

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

Un altro modo è usare le espressioni lambda come inizializzatori. Ad un certo punto nella gerarchia delle chiamate, saprai quali oggetti dovrai istanziare (ad es. Quando crei o ottieni un riferimento all'oggetto GraphicsDeviceManager). Non appena ce l'hai, passa la lambda

() => new Triangle(manager) 

ai metodi successivi, così sapranno come creare un Triangolo da quel momento in poi. Se non riesci a determinare tutti i possibili metodi di cui avrai bisogno, puoi sempre creare un dizionario di tipi che implementano IDrawable usando reflection e registrare l'espressione lambda mostrata sopra in un dizionario che puoi memorizzare in una posizione condivisa o passare a ulteriori chiamate di funzione.

Potresti farlo con il trucco dei generici, ma è ancora vulnerabile a ciò che Jon Skeet ha scritto:

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

La classe che implementa questa interfaccia deve avere un costruttore senza parametri:

public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}

Lo scopo di un'interfaccia è imporre una certa firma dell'oggetto. Non dovrebbe esplicitamente riguardare il modo in cui un oggetto funziona internamente. Pertanto, un costruttore in un'interfaccia non ha davvero senso da un punto di vista concettuale.

Ci sono alcune alternative però:

  • Crea una classe astratta che funge da implementazione predefinita minima. Quella classe dovrebbe avere i costruttori che ti aspetti di implementare le classi avere.

  • Se non ti dispiace per l'overkill, usa il modello AbstractFactory e dichiarare un metodo nell'interfaccia di classe di fabbrica che abbia il requisito firme.

  • Passa il GraphicsDeviceManager come parametro ai metodi Aggiorna e Disegna .

  • Utilizza un framework di programmazione orientata agli oggetti compositi per passare il GraphicsDeviceManager nella parte dell'oggetto che lo richiede. Questa è una soluzione piuttosto sperimentale secondo me.

La situazione che descrivi non è facile da gestire in generale. Un caso simile sarebbero le entità in un'applicazione aziendale che richiedono l'accesso al database.

non lo fai.

il costruttore fa parte della classe che può implementare un'interfaccia. L'interfaccia è solo un contratto di metodi che la classe deve implementare.

Sarebbe molto utile se fosse possibile definire i costruttori nelle interfacce.

Dato che un'interfaccia è un contratto che deve essere utilizzato nel modo specificato. Il seguente approccio potrebbe essere una valida alternativa per alcuni scenari:

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

}

Un modo per forzare una sorta di costruttore è dichiarare solo Getters nell'interfaccia, il che potrebbe quindi significare che la classe di implementazione deve avere un metodo, idealmente un costruttore, per avere il valore impostato (< code> private ly) per questo.

Sebbene non sia possibile definire una firma del costruttore in un'interfaccia, ritengo che valga la pena ricordare che questo potrebbe essere un punto da considerare in una classe astratta. Le classi astratte possono definire firme di metodi (astratte) non implementate allo stesso modo di un'interfaccia, ma possono anche avere implementato metodi e costruttori (concreti).

Il rovescio della medaglia è che, poiché si tratta di un tipo di classe, non può essere utilizzato per nessuno dei molteplici scenari di ereditarietà che un'interfaccia può.

Uso il seguente modello per renderlo antiproiettile.

  • Uno sviluppatore che deriva la sua classe dalla base non può creare accidentalmente un costruttore accessibile al pubblico
  • Lo sviluppatore della classe finale è costretto a seguire il metodo di creazione comune
  • Tutto è sicuro per il tipo, non sono richiesti casting
  • È flessibile al 100% e può essere riutilizzato ovunque, dove è possibile definire la propria base Classe.
  • Provalo, non puoi romperlo senza apportare modifiche alle classi base (eccetto se si definisce un flag obsoleto senza flag di errore impostato su true, ma anche in questo caso si ottiene un avviso)

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

Modifica Ed ecco un esempio basato sul tuo esempio di disegno che impone persino l'astrazione dell'interfaccia

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

Se ho capito correttamente OP, vogliamo far rispettare un contratto in cui GraphicsDeviceManager è sempre inizializzato dalle classi di implementazione. Avevo un problema simile e stavo cercando una soluzione migliore, ma questa è la migliore che mi viene in mente:

Aggiungi un SetGraphicsDeviceManager (GraphicsDeviceManager gdo) all'interfaccia e in questo modo le classi di implementazione saranno costrette a scrivere una logica che richiederà una chiamata dal costruttore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top