[c#] Interfaccia che definisce una firma del costruttore?



7 Answers

Non puoi Occasionalmente è un dolore, ma non si potrebbe comunque chiamarlo usando le normali tecniche.

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

Un punto su se potessi definire un costruttore all'interno di un'interfaccia, avresti problemi nel 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)
    {
    }
}
Question

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

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

modificare
Alcune persone volevano un esempio (è un progetto a tempo libero, quindi sì, è un gioco)

IDrawable
+ Aggiornamento
+ Draw

Per essere in grado di effettuare l'aggiornamento (controllare il bordo dello schermo ecc.) E disegnare da solo, sarà sempre necessario un GraphicsDeviceManager . Quindi voglio assicurarmi che l'oggetto abbia un riferimento ad esso. Questo dovrebbe appartenere al costruttore.

Ora che ho scritto questo, penso che quello che sto implementando qui è IObservable e il GraphicsDeviceManager dovrebbe prendere l' IDrawable ... Sembra che non ottengo il framework XNA, o il framework non è pensato molto bene.

modificare
Sembra esserci una certa confusione circa la mia definizione di costruttore nel contesto di un'interfaccia. Un'interfaccia non può essere istanziata quindi non ha bisogno di un costruttore. Quello che volevo definire era una firma per un costruttore. Esattamente come un'interfaccia può definire una firma di un determinato metodo, l'interfaccia potrebbe definire la firma di un costruttore.




Stavo guardando indietro a questa domanda e ho pensato a me stesso, forse stiamo attaccando 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 una classe base (astratta) lo è.

Se crei una classe base con un costruttore lì che accetta i parametri necessari, ogni classe che ne designa 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 è sfruttare i generici e il nuovo () vincolo.

Invece di esprimere il tuo costruttore come metodo / funzione, puoi esprimerlo come una classe / interfaccia di fabbrica. Se si specifica il nuovo vincolo generico () in ogni sito di chiamata che deve creare un oggetto della classe, sarà possibile passare di conseguenza gli argomenti del costruttore.

Per il tuo esempio identificabile:

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
}

È anche possibile concentrare tutti i metodi di creazione in una singola classe utilizzando l'implementazione dell'interfaccia esplicita:

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 all'inizio della gerarchia di chiamate, saprai quali oggetti dovrai istanziare (cioè quando stai creando o ricevendo un riferimento al tuo oggetto GraphicsDeviceManager). Appena lo hai, passa il lambda

() => new Triangle(manager) 

a metodi successivi in ​​modo che sappiano come creare un triangolo da quel momento in poi. Se non è possibile determinare tutti i possibili metodi necessari, è sempre possibile creare un dizionario di tipi che implementano IDrawable utilizzando la reflection e registrare l'espressione lambda mostrata sopra in un dizionario che è possibile memorizzare in un percorso condiviso o passare a ulteriori chiamate di funzione.




Se ho capito bene OP, vogliamo imporre un contratto in cui GraphicsDeviceManager è sempre inizializzato implementando le classi. Ho avuto un problema simile e stavo cercando una soluzione migliore, ma questo è il meglio che posso pensare:

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 da parte del costruttore.




Un modo per risolvere questo problema che ho trovato è quello di 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 che ho -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

Ora, ho anche bisogno di un modo per la mia classe di coda effettiva per tradurre un oggetto CloudQueueMessage su un oggetto IQueue - cioè 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 generica coda 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 soddisfi i criteri per me

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

speriamo che questo possa aiutare qualcun altro un giorno, ovviamente un sacco di codice interno rimosso per cercare di mostrare il problema e la soluzione




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 un'alternativa valida 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;
    }

}



Non puoi

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

Se hai uno stato che deve essere inizializzato, dovresti prendere in considerazione l'utilizzo di una classe base astratta.




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 metodo non astratte (astratte) allo stesso modo di un'interfaccia, ma possono anche aver implementato metodi e costruttori (concreti).

Il rovescio della medaglia è che, poiché è un tipo di classe, non può essere utilizzato per nessuno degli scenari di tipo di ereditarietà multipli che un'interfaccia può fare.




Related