Interface définissant une signature de constructeur?

c'est bizarre que c'est la première fois que je tombe sur ce problème, mais:

Comment définissez-vous un constructeur dans une interface C#?

Modifier

Certains voulaient un exemple (c'est un projet de temps libre, donc oui, c'est un jeu)

IDrawable

+Mise à jour

+ Draw

pour pouvoir mettre à jour (cochez pour le bord de l'écran etc) et se dessiner il aura toujours besoin d'un GraphicsDeviceManager . Je veux donc m'assurer que l'objet a une référence. Ceci appartiendrait au constructeur.

maintenant que j'ai écrit ceci, je pense que ce que je mets en œuvre ici c'est IObservable et le GraphicsDeviceManager devrait prendre le IDrawable ... Il semble que soit je ne comprends pas le cadre XNA, soit le cadre n'est pas très bien pensé.

Modifier

Il semble y avoir une certaine confusion au sujet de ma définition de constructeur dans le contexte d'une interface. Une interface ne peut en effet pas être instanciée et n'a donc pas besoin d'un constructeur. Ce que je voulais définir était une signature pour un constructeur. Exactement comme une interface peut définir une signature d'une méthode déterminée, l'interface pourrait définir la signature d'un constructeur.

458
demandé sur Adi Lester 2009-03-06 21:13:27

16 réponses

comme déjà noté, vous ne pouvez pas avoir de constructeurs sur une Interface. Mais comme il s'agit d'un résultat très bien classé dans Google environ 7 ans plus tard, j'ai pensé que je voudrais puce ici - spécifiquement pour montrer comment vous pourriez utiliser une classe de base abstraite en tandem avec votre Interface existante et peut-être réduire la quantité de remaniement nécessaire à l'avenir pour des situations similaires. Ce concept a déjà été fait allusion dans certains commentaires, mais j'ai pensé qu'il serait intéressant de montrer comment effectivement le faire.

Donc, vous avez votre interface principale qui ressemble à ceci:

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

créez maintenant une classe abstraite avec le constructeur que vous voulez appliquer. En fait, puisqu'il est maintenant disponible depuis le moment où vous avez écrit votre question originale, nous pouvons obtenir un peu de fantaisie ici et utiliser des génériques dans cette situation afin que nous puissions adapter ceci à d'autres interfaces qui pourraient avoir besoin de la même fonctionnalité, mais ont différentes exigences de constructeur:

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

    }
}

maintenant vous devez créer une nouvelle classe qui hérite à la fois de L'interface IDrawable et de la classe abstraite Mutinitialize:

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

alors créez juste une instance de Drawable et vous êtes bon à aller:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

ce qui est cool ici, c'est que la nouvelle classe dessinable que nous avons créée se comporte toujours comme ce que nous attendons d'un IDrawable.

Si vous avez besoin de passer plus de un paramètre du constructeur Musinitialiser, vous pouvez créer une classe qui définit les propriétés pour tous les champs que vous aurez besoin de passer.

81
répondu Dan 2017-01-16 21:57:06

vous ne pouvez pas. C'est parfois une douleur, mais on ne peut pas l'appeler en utilisant des techniques normales de toute façon.

dans un billet de blog j'ai suggéré interfaces statiques qui ne serait utilisable que dans les contraintes de type générique - mais qui pourrait être très pratique, IMO.

Un moment donné, si vous pourrait définir un constructeur au sein d'une interface, vous auriez du mal à retirer de classes:

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
répondu Jon Skeet 2014-08-17 13:53:18

une contribution très tardive démontrant un autre problème avec les constructeurs interfacés. (Je choisis cette question parce qu'elle a l'articulation la plus claire du problème). Supposons que nous aurions pu:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

où par convention la mise en œuvre du" constructeur d'interface " est remplacée par le nom du type.

maintenant faire une instance:

ICustomer a = new Person("Ernie");

dirions - nous que le contrat ICustomer est obéi?

et qu'en est-il de ceci:

interface ICustomer
{
    ICustomer(string address);
}
131
répondu Gert Arnold 2014-08-04 10:07:25

vous ne pouvez pas.

Les Interfaces

définissent les contrats que d'autres objets mettent en œuvre et n'ont donc pas d'état qui doit être initialisé.

si vous avez un État qui doit être initialisé, vous devriez envisager d'utiliser une classe de base abstraite à la place.

56
répondu Michael 2009-03-06 18:15:32

il n'est pas possible de créer une interface qui définit les constructeurs, mais il est possible de définir une interface qui force un type à avoir un constructeur sans paramètre, bien que ce soit une syntaxe très laide qui utilise des génériques... Je ne suis pas si sûr que c'est vraiment un bon modèle de codage.

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'autre part, si vous voulez tester si un type a un paramerterless constructeur, vous pouvez le faire en utilisant la réflexion:

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

Espérons que cette aide.

19
répondu Jeroen Landheer 2009-03-06 18:40:04

je regardais en arrière à cette question et je me suis dit, peut-être nous approchons ce problème de la mauvaise façon. Les Interfaces peuvent ne pas être la solution lorsqu'il s'agit de définir un constructeur avec certains paramètres... mais une classe de base (abstraite) l'est.

Si vous créez une classe de base avec un constructeur qui accepte les paramètres dont vous avez besoin, chaque classe derrives de il doit les fournir.

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
répondu Jeroen Landheer 2012-03-02 18:35:26

pour Le générique de l'usine approche me semble idéal. Vous savez que l'usine nécessite un paramètre, et il serait juste arriver que ces paramètres sont passés au constructeur de l'objet instancié.

Note, c'est juste un pseudo code vérifié par la syntaxe, il peut y avoir une mise en garde sur l'exécution que je manque ici:

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
}

exemple d'utilisation possible:

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

accordé, vous ne vous voulez les instances create via l'usine pour garantir que vous avez toujours un objet correctement initialisé. Peut-être que l'utilisation d'un cadre d'injection de dépendances comme AutoFac aurait du sens; Update() pourrait "demander" au conteneur IoC pour un nouvel objet GraphicsDeviceManager.

5
répondu Matthew 2012-06-28 21:17:33

une façon de résoudre ce problème que j'ai trouvé est de séparer la construction en une usine séparée. Par exemple, j'ai une classe abstraite appelée IQueueItem, et j'ai besoin d'un moyen pour traduire cet objet vers et depuis un autre objet (CloudQueueMessage). Donc sur l'interface IQueueItem j'ai -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

maintenant, j'ai aussi besoin D'un moyen pour ma classe de file d'attente réelle de traduire un message CloudQueueMessage retour à un IQueueItem-ie la nécessité d'une construction statique comme IQueueItem objMessage = ItemType.FromMessage. Au lieu de cela, j'ai défini une autre interface IQueueFactory -

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

maintenant je peux enfin écrire ma classe de file d'attente générique sans la nouvelle contrainte () qui dans mon cas était la question 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;
    }
}

maintenant je peux créer une instance qui répond aux critères pour moi

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

espérons que cela aide quelqu'un d'autre un jour, évidemment beaucoup de code interne retiré à essayer pour montrer le problème et la solution

5
répondu JTtheGeek 2012-06-29 03:43:25

une façon de résoudre ce problème est de tirer parti des génériques et de la nouvelle() contrainte.

au Lieu d'exprimer votre constructeur comme une méthode/fonction, vous pouvez l'exprimer comme une fabrique de classe ou interface. Si vous spécifiez la nouvelle contrainte() generic sur chaque site d'appel qui a besoin de créer un objet de votre classe, vous serez en mesure de passer les arguments de constructeur en conséquence.

pour votre exemple 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);
    } 
}

Maintenant, quand vous l'utilisez:

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
}

vous pouvez même concentrer toutes les méthodes de création dans une seule classe en utilisant l'implémentation explicite de l'interface:

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

pour l'utiliser:

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
}

une autre façon est d'utiliser les expressions lambda comme initialisateurs. À un certain point au début de la hiérarchie d'appel, vous saurez quels objets vous aurez besoin d'instancier (i.e. quand vous créez ou obtenez une référence à votre GraphicsDeviceManager). Dès que vous l'avez, passez le lambda

() => new Triangle(manager) 

aux méthodes suivantes afin qu'ils sachent comment créer un Triangle à partir de là. Si vous ne pouvez pas déterminer toutes les méthodes possibles dont vous aurez besoin, vous pouvez toujours créer un dictionnaire de types qui mettent en œuvre IDrawable en utilisant la réflexion et enregistrer l'expression lambda ci-dessus dans un dictionnaire que vous pouvez stocker dans un emplacement partagé ou passer le long à d'autres appels de fonction.

3
répondu Cesar 2016-04-19 20:46:46

vous pourriez le faire avec Generics trick, mais il est encore vulnérable à ce que Jon Skeet a écrit:

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

classe qui implémente cette interface doit avoir un constructeur sans paramètres:

public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}
2
répondu ghord 2013-08-14 07:35:24

, Non.

le constructeur fait partie de la classe qui peut implémenter une interface. L'interface est juste un contrat de méthodes que la classe doit implémenter.

0
répondu royatl 2009-03-06 18:20:53

il serait très utile s'il était possible de définir les constructeurs dans les interfaces.

étant Donné qu'une interface est un contrat qui doit être utilisé dans la façon déterminée. L'approche suivante pourrait constituer une solution de rechange viable pour certains scénarios:

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
répondu Lea Hayes 2012-05-18 01:15:23

une façon de forcer une sorte de constructeur est de déclarer seulement Getters dans l'interface, ce qui pourrait alors signifier que la classe d'implémentation doit avoir une méthode, idéalement un constructeur, pour avoir la valeur fixée ( private ly) pour elle.

0
répondu Kunal 2014-02-12 16:27:45

alors que vous ne pouvez pas définir une signature de constructeur dans une interface, je pense qu'il est intéressant de mentionner que cela peut être un endroit pour considérer une classe abstraite. Les classes abstraites peuvent définir des signatures de méthodes (abstraites) Non mises en œuvre de la même manière qu'une interface, mais peuvent aussi avoir mis en œuvre des méthodes (concrètes) et des constructeurs.

l'inconvénient est que, parce qu'il s'agit d'un type de classe, il ne peut pas être utilisé pour l'un des scénarios de type d'héritage multiple qu'un l'interface peut.

0
répondu IrishLagger 2016-11-06 19:29:59

le but d'une interface est d'imposer une certaine signature d'objet. Il ne devrait pas explicitement se préoccuper de la façon dont un objet fonctionne en interne. Par conséquent, un constructeur dans une interface n'a pas vraiment de sens d'un point de vue conceptuel.

il y a cependant quelques alternatives:

  • créer une classe abstraite qui agit comme une implémentation par défaut minimale. Cette classe devrait avoir les constructeurs que vous attendez mise en œuvre des classes avoir.

  • déclarer une méthode dans l'interface de classe d'usine qui a le requis signature.

  • passez le GraphicsDeviceManager comme paramètre aux méthodes Update et Draw .

  • utiliser un cadre de programmation orienté objet pour passer le GraphicsDeviceManager dans la partie de l'objet qui l'exige. C'est une solution assez expérimentale à mon avis.

la situation que vous décrivez n'est pas facile à gérer en général. Un cas similaire pourrait être celui des entités d'une application commerciale qui ont besoin d'accéder à la base de données.

0
répondu MauganRa 2018-02-18 18:31:50

si j'ai bien compris OP, nous voulons appliquer un contrat où GraphicsDeviceManager est toujours initialisé par l'implémentation de classes. J'ai eu un problème similaire et je cherchais une meilleure solution, mais c'est la meilleure que je puisse imaginer:

Ajouter un Segraphicsdevicemanager (GraphicsDeviceManager gdo) à l'interface, et de cette façon les classes d'implémentation seront forcées d'écrire une logique qui nécessitera un appel du constructeur.

-2
répondu Kabali 2016-12-20 18:58:22