10 Stimmen

Wie kann man für Typen, die eine Schnittstelle implementieren, einen Konstruktor ohne Parameter verlangen?

Gibt es einen Weg?

Ich brauche alle Typen, die eine bestimmte Schnittstelle implementieren, um einen parameterlosen Konstruktor haben, kann es getan werden?

Ich entwickle den Basiscode für andere Entwickler in meinem Unternehmen, die ihn für ein bestimmtes Projekt verwenden sollen.

Es gibt einen Prozess, der Instanzen von Typen (in verschiedenen Threads) erstellt, die bestimmte Aufgaben ausführen, und ich brauche diese Typen, um einem bestimmten Vertrag (ergo, die Schnittstelle) zu folgen.

Die Schnittstelle wird intern in der Baugruppe sein

Wenn Sie einen Vorschlag für dieses Szenario ohne Schnittstellen haben, werde ich ihn gerne in Betracht ziehen...

22voto

Brad Wilson Punkte 64944

Ich will nicht zu direkt sein, aber Sie haben den Zweck von Schnittstellen missverstanden.

Eine Schnittstelle bedeutet, dass mehrere Personen sie in ihren eigenen Klassen implementieren und dann Instanzen dieser Klassen an andere Klassen zur Verwendung übergeben können. Die Erstellung schafft eine unnötig starke Kopplung.

Es hört sich so an, als bräuchte man wirklich eine Art Registrierungssystem, entweder um Instanzen von verwendbaren Klassen zu registrieren, die die Schnittstelle implementieren, oder von Fabriken, die diese Objekte auf Anfrage erstellen können.

6voto

aku Punkte 118808

Sie können verwenden Typ-Parameter-Beschränkung

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

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

5voto

Chris Ammerman Punkte 14591

Juan,

Leider gibt es keine Möglichkeit, dies in einer stark typisierten Sprache zu umgehen. Sie werden nicht in der Lage sein, zur Kompilierzeit sicherzustellen, dass die Klassen von Ihrem Activator-basierten Code instanziiert werden können.

(Anm. d. Red.: eine fehlerhafte Alternativlösung wurde entfernt)

Der Grund dafür ist, dass es leider nicht möglich ist, Schnittstellen, abstrakte Klassen oder virtuelle Methoden in Kombination mit Konstruktoren oder statischen Methoden zu verwenden. Der kurze Grund ist, dass erstere keine expliziten Typinformationen enthalten und letztere explizite Typinformationen erfordern.

Konstruktoren und statische Methoden muss zum Zeitpunkt des Aufrufs über explizite (direkt im Code enthaltene) Typinformationen verfügen. Dies ist erforderlich, da es keine Instanz der betroffenen Klasse gibt, die von der Laufzeit abgefragt werden kann, um den zugrundeliegenden Typ zu erhalten, den die Laufzeit benötigt, um zu bestimmen, welche konkrete Methode aufgerufen werden soll.

Der ganze Sinn einer Schnittstelle, einer abstrakten Klasse oder einer virtuellen Methode besteht darin, einen Funktionsaufruf zu ermöglichen ohne explizite Typinformationen, und dies wird durch die Tatsache ermöglicht, dass eine Instanz referenziert wird, die "versteckte" Typinformationen hat, die dem aufrufenden Code nicht direkt zur Verfügung stehen. Diese beiden Mechanismen schließen sich also ganz einfach gegenseitig aus. Sie können nicht zusammen verwendet werden, denn wenn man sie mischt, hat man am Ende nirgendwo konkrete Typinformationen, was bedeutet, dass die Laufzeit keine Ahnung hat, wo die Funktion zu finden ist, die man aufzurufen wünscht.

5voto

Chris Ammerman Punkte 14591

sagte Juan Manuel:

Das ist einer der Gründe, warum ich nicht verstehe, warum es nicht Teil des Vertrags in der Schnittstelle sein kann.

Es ist ein indirekter Mechanismus. Die Generik erlaubt es Ihnen, zu "schummeln" und Typinformationen zusammen mit der Schnittstelle zu senden. Das Entscheidende dabei ist, dass sich die Einschränkung nicht auf die Schnittstelle bezieht, mit der Sie direkt arbeiten. Es handelt sich nicht um eine Einschränkung für die Schnittstelle selbst, sondern für einen anderen Typ, der auf der Schnittstelle "mitfährt". Das ist leider die beste Erklärung, die ich anbieten kann.

Zur Veranschaulichung dieser Tatsache möchte ich auf eine Lücke hinweisen, die ich im Code von aku festgestellt habe. Es ist möglich, eine Klasse zu schreiben, die sich gut kompilieren lässt, aber zur Laufzeit nicht funktioniert, wenn man versucht, sie zu instanziieren:

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

Etwas leitet sich von ITest<T> ab, implementiert aber keinen parameterlosen Konstruktor. Es wird gut kompiliert, weil String einen parameterlosen Konstruktor implementiert. Auch hier gilt die Einschränkung für T und damit für String, und nicht für ITest oder Something. Da die Einschränkung auf T erfüllt ist, wird dies kompiliert. Aber es wird zur Laufzeit fehlschlagen.

Zu verhindern einige Instanzen dieses Problems, müssen Sie eine weitere Bedingung zu T hinzufügen, wie unten:

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

Beachten Sie die neue Einschränkung: T : ITest<T>. Diese Einschränkung legt fest, dass das, was Sie in den Argument-Parameter von ITest<T> übergeben muss auch ableiten. von ITest<T>.

Dennoch wird dies nicht verhindern, dass alle Fälle des Lochs. Der folgende Code lässt sich gut kompilieren, da A einen parameterlosen Konstruktor hat. Aber da der parameterlose Konstruktor von B privat ist, wird die Instanziierung von B mit Ihrem Prozess zur Laufzeit fehlschlagen.

public class A : ITest<A>
{
}

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

4voto

munificent Punkte 11450

Sie brauchen also eine Sache die Instanzen eines unbekannten Typs erzeugen kann, der eine Schnittstelle implementiert. Sie haben grundsätzlich drei Möglichkeiten: ein Fabrikobjekt, ein Typobjekt oder ein Delegat. Hier sind die Gegebenheiten:

public interface IInterface
{
    void DoSomething();
}

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

Die Verwendung von Type ist ziemlich hässlich, macht aber in manchen Szenarien Sinn:

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

Das größte Problem dabei ist, dass man zur Kompilierzeit keine Garantie hat, dass Foo tatsächlich hat einen Standard-Konstruktor. Außerdem ist die Reflexion ein wenig langsam, wenn es sich um leistungsrelevanten Code handelt.

Die häufigste Lösung ist die Verwendung einer Fabrik:

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

In diesem Fall ist IFactory das, was wirklich zählt. Factory ist nur eine Komfortklasse für Klassen, die tun bieten einen Standardkonstruktor. Dies ist die einfachste und oft beste Lösung.

Die dritte, derzeit noch unübliche, aber in Zukunft wahrscheinlich häufiger anzutreffende Lösung ist die Verwendung eines Delegaten:

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

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

Der Vorteil hier ist, dass der Code kurz und einfach ist, mit jede Methode der Konstruktion, und (mit Schließungen) können Sie leicht weitergeben zusätzliche Daten benötigt, um die Objekte zu konstruieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X