642 Stimmen

Schnittstelle, die eine Konstruktorsignatur definiert?

Es ist seltsam, dass ich zum ersten Mal mit diesem Problem konfrontiert werde, aber:

Wie definiert man einen Konstruktor in einer C#-Schnittstelle?

編集
Einige Leute wollten ein Beispiel (es ist ein Freizeitprojekt, also ja, es ist ein Spiel)

IDrawable
+Aktualisierung
+Zeichnen

Um sich selbst zu aktualisieren (auf den Rand des Bildschirms zu prüfen usw.) und zu zeichnen, benötigt es immer eine GraphicsDeviceManager . Ich möchte also sicherstellen, dass das Objekt einen Verweis darauf hat. Dies würde in den Konstruktor gehören.

Jetzt, wo ich das aufgeschrieben habe, denke ich, dass ich hier Folgendes umsetze IObservable und die GraphicsDeviceManager sollte die IDrawable ... Es scheint, dass ich entweder das XNA-Framework nicht verstehe, oder dass das Framework nicht sehr gut durchdacht ist.

編集
Es scheint einige Verwirrung über meine Definition von Konstruktor im Zusammenhang mit einer Schnittstelle zu geben. Eine Schnittstelle kann tatsächlich nicht instanziiert werden und braucht daher keinen Konstruktor. Was ich definieren wollte, war eine Signatur für einen Konstruktor. Genau wie eine Schnittstelle eine Signatur einer bestimmten Methode definieren kann, könnte die Schnittstelle die Signatur eines Konstruktors definieren.

3 Stimmen

Anstatt eine Schnittstelle zu haben, die Ihren Konstruktor definiert, haben Sie stattdessen eine Schnittstelle, die Ihre Fabrikmethoden definiert.

343voto

Jon Skeet Punkte 1325502

Das können Sie nicht. Es ist zwar gelegentlich lästig, aber mit normalen Techniken wäre es ohnehin nicht möglich, es zu rufen.

In einem Blogbeitrag habe ich vorgeschlagen statische Schnittstellen die nur in generischen Typbeschränkungen verwendbar wäre - aber IMO wirklich praktisch sein könnte.

Ein Punkt, wenn Sie könnte einen Konstruktor innerhalb einer Schnittstelle zu definieren, hätten Sie Probleme bei der Ableitung von Klassen:

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

5 Stimmen

Ich könnte in der Tat Probleme sehen, aber das gilt auch für alle anderen von Ihnen definierten Methoden. Normalerweise ist NotSupportedException der einzige Ausweg.

7 Stimmen

@boris: Der Unterschied ist, dass es immer eine etwas mit normaler Vererbung aufgerufen werden, was der Compiler garantiert. In diesem Fall gibt es etwas, das da sein "sollte", aber nicht ist.

1 Stimmen

Wenn Typ A von Typ B erbt, der seinerseits die Schnittstelle I implementiert, stellt A dann nicht die Eigenschaften und Methoden von I zur Verfügung? Aus Ihrem vorherigen Satz schließe ich, dass dies nicht der Fall ist, aber mir fällt kein Gegenbeispiel ein.

206voto

Dan Punkte 3313

Wie bereits erwähnt, können Sie keine Konstruktoren auf einer Schnittstelle haben. Aber da dies nach 7 Jahren ein so hochrangiges Ergebnis in Google ist, dachte ich, ich würde mich hier einmischen - insbesondere, um zu zeigen, wie man eine abstrakte Basisklasse in Verbindung mit einer bestehenden Schnittstelle verwenden kann und vielleicht die Menge an Refactoring, die in Zukunft für ähnliche Situationen erforderlich ist, reduzieren kann. Dieses Konzept wurde bereits in einigen Kommentaren angedeutet, aber ich dachte, es wäre es wert, zu zeigen, wie man es tatsächlich macht.

Sie haben also Ihre Hauptschnittstelle, die bisher wie folgt aussieht:

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

Erstellen Sie nun eine abstrakte Klasse mit dem Konstruktor, den Sie erzwingen wollen. Da dies seit dem Zeitpunkt, an dem Sie Ihre ursprüngliche Frage geschrieben haben, verfügbar ist, können wir uns hier etwas einfallen lassen und in dieser Situation Generics verwenden, so dass wir dies an andere Schnittstellen anpassen können, die möglicherweise die gleiche Funktionalität benötigen, aber andere Anforderungen an den Konstruktor haben:

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

    }
}

Nun müssen Sie eine neue Klasse erstellen, die sowohl von der Schnittstelle IDrawable als auch von der abstrakten Klasse MustInitialize erbt:

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

Dann erstellen Sie einfach eine Instanz von Drawable und schon kann es losgehen:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

Das Tolle daran ist, dass sich die neue Drawable-Klasse, die wir erstellt haben, genau so verhält, wie wir es von einer IDrawable erwarten würden.

Wenn Sie mehr als einen Parameter an den MustInitialize-Konstruktor übergeben müssen, können Sie eine Klasse erstellen, die Eigenschaften für alle Felder definiert, die Sie übergeben müssen.

24 Stimmen

Es ist wichtig zu betonen, dass Sie keine "MustInitialize"-Klasse erstellen können, die alle Fälle abdeckt, da C# keine Mehrfachvererbung zulässt. Das heißt, wenn Ihre Klasse eine abstrakte Klasse erbt, kann sie nicht auch eine andere Klasse erben.

4 Stimmen

Das ist wahr @Skarlot, es ist keine Patentlösung. Bei dem von Ihnen geschilderten Problem wäre es jedoch hoffentlich sinnvoll, die abstrakte Klasse, die Sie bereits erben, direkt zu ändern. Aber es gibt immer noch Situationen, in denen das nicht möglich und/oder angemessen ist, so dass ein tiefergehendes Entwurfsmuster erforderlich wäre.

1 Stimmen

In C# können Sie nur von einer Basisklasse erben, so dass die Vererbung von MustInitialize verhindert, dass Sie von anderen Klassen erben. Eine Alternative wäre eine Methode, die wie ein Konstuktor wirkt - Einstellung der Klassen Eigenschaften und Felder, vielleicht mit Sentinels, die verhindern, dass es zweimal erfolgreich aufgerufen wird, oder die Klasse verwendet werden, bis diese Methode aufgerufen wird.

151voto

Gert Arnold Punkte 99327

Ein sehr später Beitrag, der ein weiteres Problem mit verknüpften Konstruktoren aufzeigt. (Ich habe diese Frage ausgewählt, weil sie das Problem am deutlichsten formuliert). Angenommen, wir könnten haben:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Dabei wird die Implementierung des "Schnittstellenkonstruktors" vereinbarungsgemäß durch den Typnamen ersetzt.

Erstellen Sie nun eine Instanz:

ICustomer a = new Person("Ernie");

Würden wir sagen, dass der Vertrag ICustomer gehorcht wird?

Und was ist damit?

interface ICustomer
{
    ICustomer(string address);
}

24 Stimmen

Wenn Sie eine Methode mit der gleichen Signatur und dem gleichen Namen in ICustomer und IPerson haben, ist dieses Problem auch dort. Ich weiß nicht, wie das helfen soll. Interfaces sind nicht für "nur meine Definition". Es ist für "include me at any cost"

7 Stimmen

@nawfal Der Punkt ist, dass eine Schnittstelle niemals verlangt, dass eine Methode ausgeführt nur, dass sie existieren sollte. Sie kann niemals einen Zustand garantieren. Im Gegensatz dazu verlangt eine "Konstruktorschnittstelle", dass etwas getan (ausgeführt) wird, wenn ein Objekt konstruiert wird. Dies kann niemals garantiert werden, wenn es verschiedene Schnittstellen gibt.

9 Stimmen

@GertArnold eine Methode erfüllt ihre Aufgabe, während ein Konstruktor seine Aufgabe erfüllt. Ich verstehe nicht, was hier der Unterschied ist. Interfaces machen einen Vertrag, der besagt, dass "meine Implementierung da sein soll", nicht dass "meine die einzige Implementierung sein soll". Ich würde sagen, aus Gründen der Konsistenz sollte dies für Konstruktoren, Methoden und Eigenschaften gelten.

69voto

Michael Punkte 52790

Das können Sie nicht.

Schnittstellen definieren Verträge, die von anderen Objekten implementiert werden und haben daher keinen Zustand, der initialisiert werden muss.

Wenn Sie einen Zustand haben, der initialisiert werden muss, sollten Sie stattdessen eine abstrakte Basisklasse verwenden.

13 Stimmen

Warum kann ein Vertrag nicht einen Zustand haben?

15 Stimmen

Weil der Vertrag Sie zu einer bestimmten Leistung verpflichtet Verhalten . Die Art und Weise, wie Schnittstellen verwendet werden, setzt voraus, dass gemeinsame Verhaltensweisen extrahiert werden, und das ist nicht vom Zustand abhängig (der dann ein Detail der Implementierung wäre).

1 Stimmen

Es scheint, dass wir neben den Schnittstellen einen separaten Mechanismus wie einen "Vertrag" verwenden könnten, um von einem Objekt zu verlangen, dass es bestimmte Funktionen wie Konstruktoren und statische Methoden/Eigenschaften implementiert, die nicht über den Vertrag aufgerufen werden (wie es bei einer Schnittstelle der Fall wäre). Feature-Vorschlag für die nächste .Net-Version?

21voto

Jeroen Landheer Punkte 8240

Ich habe mir diese Frage noch einmal angesehen und mir gedacht, dass wir das Problem vielleicht auf die falsche Weise angehen. Schnittstellen sind vielleicht nicht der richtige Weg, wenn es um die Definition eines Konstruktors mit bestimmten Parametern geht... aber eine (abstrakte) Basisklasse schon.

Wenn Sie eine Basisklasse mit einem Konstruktor erstellen, der die benötigten Parameter akzeptiert, muss jede Klasse, die davon abgeleitet ist, diese Parameter bereitstellen.

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

0 Stimmen

So habe ich auch dieses Problem gelöst. Meine Schnittstelle definiert, was die Klasse können muss, aber meine abstrakte Basisklasse erzwingt die Konstruktorkomponente der Klasse.

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