Comment pouvez-vous exiger un constructeur sans paramètres pour les types implémentant une interface ?

StackOverflow https://stackoverflow.com/questions/26903

Question

Y a-t-il un moyen ?

J'ai besoin que tous les types qui implémentent une interface spécifique aient un constructeur sans paramètre, est-ce possible ?

Je développe le code de base que d'autres développeurs de mon entreprise pourront utiliser dans un projet spécifique.

Il existe un processus qui créera des instances de types (dans différents threads) qui effectuent certaines tâches, et j'ai besoin que ces types suivent un contrat spécifique (ergo, l'interface).

L'interface sera interne à l'assembly

Si vous avez une suggestion pour ce scénario sans interfaces, je la prendrai volontiers en considération...

Était-ce utile?

La solution

Juan Manuel a dit :

c'est une des raisons pour lesquelles je ne comprends pas pourquoi cela ne peut pas faire partie du contrat dans l'interface

C'est un mécanisme indirect.Le générique vous permet de "tricher" et d'envoyer des informations de type avec l'interface.La chose essentielle à retenir ici est que la contrainte ne concerne pas l’interface avec laquelle vous travaillez directement.Ce n'est pas une contrainte sur l'interface elle-même, mais sur un autre type qui "accompagnera" l'interface.C'est la meilleure explication que je puisse offrir, j'en ai peur.

A titre d'illustration de ce fait, je signalerai un trou que j'ai remarqué dans le code d'aku.Il est possible d'écrire une classe qui se compilerait correctement mais échouerait au moment de l'exécution lorsque vous essayez de l'instancier :

public class Something : ITest<String>
{
  private Something() { }
}

Quelque chose dérive de ITest<T>, mais n'implémente aucun constructeur sans paramètre.Il compilera correctement, car String implémente un constructeur sans paramètre.Encore une fois, la contrainte est sur T, et donc String, plutôt que sur ITest ou Something.Puisque la contrainte sur T est satisfaite, cela sera compilé.Mais cela échouera au moment de l’exécution.

Pour prévenir quelques Dans certaines instances de ce problème, vous devez ajouter une autre contrainte à T, comme ci-dessous :

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

Notez la nouvelle contrainte :T :ITest<T>.Cette contrainte spécifie que ce que vous transmettez dans le paramètre d'argument de ITest<T> doit aussi dériver à partir de ITest<T>.

Cela n'empêchera quand même pas tous cas du trou.Le code ci-dessous se compilera correctement, car A a un constructeur sans paramètre.Mais comme le constructeur sans paramètre de B est privé, l'instanciation de B avec votre processus échouera au moment de l'exécution.

public class A : ITest<A>
{
}

public class B : ITest<A>
{
  private B() { }
}

Autres conseils

Je ne veux pas être trop direct, mais vous avez mal compris le but des interfaces.

Une interface signifie que plusieurs personnes peuvent l'implémenter dans leurs propres classes, puis transmettre des instances de ces classes à d'autres classes pour les utiliser.La création crée un couplage fort inutile.

Il semble que vous ayez vraiment besoin d'une sorte de système d'enregistrement, soit pour que les gens enregistrent des instances de classes utilisables qui implémentent l'interface, soit d'usines qui peuvent créer lesdits éléments sur demande.

Juan,

Malheureusement, il n'existe aucun moyen de contourner ce problème dans un langage fortement typé.Vous ne pourrez pas garantir au moment de la compilation que les classes pourront être instanciées par votre code basé sur Activator.

(éd. :supprimé une solution alternative erronée)

La raison en est que, malheureusement, il n'est pas possible d'utiliser des interfaces, des classes abstraites ou des méthodes virtuelles en combinaison avec des constructeurs ou des méthodes statiques.La courte raison est que les premiers ne contiennent aucune information de type explicite et que les seconds nécessitent des informations de type explicites.

Constructeurs et méthodes statiques doit avoir des informations de type explicites (juste là dans le code) disponibles au moment de l'appel.Ceci est nécessaire car il n'existe aucune instance de la classe impliquée qui puisse être interrogée par le runtime pour obtenir le type sous-jacent, dont le runtime a besoin pour déterminer la méthode concrète réelle à appeler.

L’intérêt d’une interface, d’une classe abstraite ou d’une méthode virtuelle est de pouvoir effectuer un appel de fonction sans des informations de type explicites, et cela est rendu possible par le fait qu'il existe une instance référencée, qui contient des informations de type "cachées" qui ne sont pas directement disponibles pour le code appelant.Ces deux mécanismes s’excluent donc tout simplement.Ils ne peuvent pas être utilisés ensemble car lorsque vous les mélangez, vous vous retrouvez avec aucune information de type concrète, ce qui signifie que le runtime n'a aucune idée de l'endroit où trouver la fonction que vous lui demandez d'appeler.

Vous pouvez utiliser contrainte de paramètre de type

interface ITest<T> where T: new()
{
    //...
}

class Test: ITest<Test>
{
    //...
}

Il te faut donc un chose qui peut créer des instances d'un type inconnu qui implémente une interface.Vous avez essentiellement trois options :un objet usine, un objet Type ou un délégué.Voici les données :

public interface IInterface
{
    void DoSomething();
}

public class Foo : IInterface
{
    public void DoSomething() { /* whatever */ }
}

Utiliser Type est plutôt moche, mais cela a du sens dans certains scénarios :

public IInterface CreateUsingType(Type thingThatCreates)
{
    ConstructorInfo constructor = thingThatCreates.GetConstructor(Type.EmptyTypes);
    return (IInterface)constructor.Invoke(new object[0]);
}

public void Test()
{
    IInterface thing = CreateUsingType(typeof(Foo));
}

Le plus gros problème, c'est qu'au moment de la compilation, vous n'avez aucune garantie que Foo réellement a un constructeur par défaut.De plus, la réflexion est un peu lente s'il s'agit d'un code critique en termes de performances.

La solution la plus courante consiste à utiliser une usine :

public interface IFactory
{
    IInterface Create();
}

public class Factory<T> where T : IInterface, new()
{
    public IInterface Create() { return new T(); }
}

public IInterface CreateUsingFactory(IFactory factory)
{
    return factory.Create();
}

public void Test()
{
    IInterface thing = CreateUsingFactory(new Factory<Foo>());
}

Dans ce qui précède, IFactory est ce qui compte vraiment.Factory n'est qu'une classe pratique pour les classes qui faire fournir un constructeur par défaut.C’est la solution la plus simple et souvent la meilleure.

La troisième solution actuellement peu courante mais susceptible de devenir plus courante consiste à utiliser un délégué :

public IInterface CreateUsingDelegate(Func<IInterface> createCallback)
{
    return createCallback();
}

public void Test()
{
    IInterface thing = CreateUsingDelegate(() => new Foo());
}

L'avantage ici est que le code est court et simple, il peut fonctionner avec n'importe lequel méthode de construction et (avec des fermetures) vous permet de transmettre facilement les données supplémentaires nécessaires à la construction des objets.

Appelez une méthode RegisterType avec le type et contraignez-la à l’aide de génériques.Ensuite, au lieu de parcourir les assemblys pour trouver les implémenteurs ITest, stockez-les simplement et créez à partir de là.

void RegisterType<T>() where T:ITest, new() {
}

Je ne pense pas.

Vous ne pouvez pas non plus utiliser une classe abstraite pour cela.

Je voudrais rappeler à tous que :

  1. L'écriture d'attributs dans .NET est facile
  2. Il est facile d'écrire des outils d'analyse statique dans .NET qui garantissent la conformité aux normes de l'entreprise.

Écrire un outil pour récupérer toutes les classes concrètes qui implémentent une certaine interface/ont un attribut et vérifier qu'il dispose d'un constructeur sans paramètre prend environ 5 minutes d'effort de codage.Vous l'ajoutez à votre étape post-construction et vous disposez désormais d'un cadre pour toutes les autres analyses statiques que vous devez effectuer.

Le langage, le compilateur, l'EDI, votre cerveau, ce sont tous des outils.Utilise les!

Non, tu ne peux pas faire ça.Peut-être que pour votre situation, une interface d'usine serait utile ?Quelque chose comme:

interface FooFactory {
    Foo createInstance();
}

Pour chaque implémentation de Foo, vous créez une instance de FooFactory qui sait comment la créer.

Vous n'avez pas besoin d'un constructeur sans paramètre pour que l'Activator instancie votre classe.Vous pouvez avoir un constructeur paramétré et transmettre tous les paramètres de l'activateur.Vérifier MSDN à ce sujet.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top