tutorial - interface in c#




Interface définissant une signature de constructeur? (11)

Bien que vous ne puissiez pas définir une signature de constructeur dans une interface, je pense qu'il vaut la peine de mentionner que cela peut être un endroit pour considérer une classe abstraite. Les classes abstraites peuvent définir des signatures de méthode non implémentées (abstraites) de la même manière qu'une interface, mais peuvent également avoir implémenté des méthodes et des constructeurs (concrets).

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

C'est bizarre que c'est la première fois que je suis tombé sur ce problème, mais:

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

modifier
Certaines personnes voulaient un exemple (c'est un projet de temps libre, alors oui, c'est un jeu)

IDrawable
+ Mise à jour
+ Draw

Pour être en mesure de mettre à jour (vérifier le bord de l'écran, etc) et dessiner lui-même, il aura toujours besoin d'un GraphicsDeviceManager . Donc je veux m'assurer que l'objet a une référence. Cela appartiendrait au constructeur.

Maintenant que j'ai écrit ceci, je pense que ce que IObservable ici est IObservable et que GraphicsDeviceManager devrait prendre l' IDrawable ... Il semble que je n'ai pas le framework XNA, ou que le framework n'est pas très bien pensé.

modifier
Il semble y avoir une certaine confusion au sujet de ma définition du 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 certaine méthode, l'interface pourrait définir la signature d'un constructeur.


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

Étant donné qu'une interface est un contrat qui doit être utilisé de la manière spécifiée. L'approche suivante pourrait être une alternative 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;
    }

}

Il n'est pas possible de créer une interface définissant des constructeurs, mais il est possible de définir une interface qui oblige un type à avoir un constructeur paramétrable, bien que ce soit une syntaxe très moche qui utilise des génériques ... Je ne suis pas si sûr c'est vraiment un bon schéma 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'un autre côté, si vous voulez tester si un type a un constructeur paramerterless, 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;
  }
}

J'espère que cela t'aides.


Je regardais en arrière cette question et je me suis dit, peut-être que nous sommes en train d'aborder ce problème de la mauvaise façon. Les interfaces peuvent ne pas être le chemin à parcourir quand 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 qui en découle 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
  {
  }
}

Le but d'une interface est d'imposer une certaine signature d'objet. Il ne devrait pas explicitement être concerné par 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 quelques alternatives cependant:

  • Créez une classe abstraite qui agit comme une implémentation par défaut minimale. Cette classe doit avoir les constructeurs que vous attendez d'implémentation des classes.

  • Si cela ne vous dérange pas, utilisez le motif AbstractFactory et déclarez une méthode dans l'interface de classe d'usine qui a les signatures requises.

  • Passez le GraphicsDeviceManager tant que paramètre aux méthodes Update et Draw .

  • Utilisez une structure de programmation orientée objet de composition pour transmettre GraphicsDeviceManager dans la partie de l'objet qui le requiert. 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 serait les entités dans une application commerciale qui nécessitent un accès à la base de données.


Si j'ai bien compris OP, nous voulons appliquer un contrat où GraphicsDeviceManager est toujours initialisé en implémentant des classes. J'ai eu un problème similaire et je cherchais une meilleure solution, mais c'est le meilleur que je peux penser:

Ajoutez un SetGraphicsDeviceManager (GraphicsDeviceManager gdo) à l'interface, et ainsi les classes d'implémentation seront forcées d'écrire une logique qui nécessitera un appel du constructeur.


Tu ne peux pas.

Les interfaces définissent des contrats implémentés par d'autres objets et n'ont donc aucun état à initialiser.

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


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 puissions avoir:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Où, par convention, l'implémentation du "constructeur d'interface" est remplacée par le nom du type.

Maintenant, faites une instance:

ICustomer a = new Person("Ernie");

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

Et à propos de ça:

interface ICustomer
{
    ICustomer(string address);
}

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

Au lieu d'exprimer votre constructeur en tant que méthode / fonction, vous pouvez l'exprimer en tant que classe / interface d'usine. Si vous spécifiez la contrainte générique new () sur chaque site d'appel qui a besoin de créer un objet de votre classe, vous pourrez passer des arguments constructeurs 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 classe unique en utilisant une implémentation d'interface explicite:

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 consiste à utiliser des expressions lambda en tant qu'initialisateurs. À un certain point au début de la hiérarchie des appels, vous saurez quels objets vous devrez instancier (c.-à-d. Lorsque vous créez ou obtenez une référence à votre objet 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 dès lors. Si vous ne pouvez pas déterminer toutes les méthodes dont vous aurez besoin, vous pouvez toujours créer un dictionnaire de types qui implémentent 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 transmettre à autres appels de fonction.


Une façon de résoudre ce problème, j'ai trouvé est de séparer la construction dans une usine séparée. Par exemple, j'ai une classe abstraite appelée IQueueItem, et j'ai besoin d'un moyen de 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 également besoin d'un moyen pour ma classe de file d'attente réelle de traduire un CloudQueueMessage en IQueueItem - c'est-à-dire le besoin 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 contrainte new () qui dans mon cas était le problème 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;
    }
}

maintenant je peux créer une instance qui satisfait les critères pour moi

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

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


tu ne le fais pas.

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.







constructor