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.

20voto

Jeroen Landheer Punkte 8240

Es ist nicht möglich, eine Schnittstelle zu erstellen, die Konstruktoren definiert, aber es ist Es ist möglich, eine Schnittstelle zu definieren, die einen Typ dazu zwingt, einen Konstruktor ohne Parameter zu haben, auch wenn es eine sehr hässliche Syntax ist, die Generics verwendet... Ich bin mir nicht sicher, ob dies wirklich ein gutes Kodierungsmuster ist.

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
}

Wenn Sie hingegen testen wollen, ob ein Typ einen Konstruktor ohne Parameter hat, können Sie dies mit Hilfe von Reflection tun:

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

Ich hoffe, das hilft.

1 Stimmen

Ich würde den Schnittstellenkonstruktor verwenden, um sicherzustellen, dass einige Argumente defenatly festgelegt sind (durch den Konstruktor), so dass eine parameterlose ctor nicht wirklich, was ich suche.

8voto

Cesar Punkte 1976

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, Generika und die new()-Beschränkung zu nutzen.

Anstatt Ihren Konstruktor als Methode/Funktion auszudrücken, können Sie ihn als Fabrikklasse/Interface ausdrücken. Wenn Sie die generische Einschränkung new() auf jeder Aufrufsite angeben, die ein Objekt Ihrer Klasse erstellen muss, können Sie die Konstruktorargumente entsprechend übergeben.

Für Ihr IDrawable-Beispiel:

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

Wenn Sie es jetzt benutzen:

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
}

Sie können sogar alle Erstellungsmethoden in einer einzigen Klasse konzentrieren, indem Sie eine explizite Schnittstellenimplementierung verwenden:

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

Zur Verwendung:

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
}

Eine andere Möglichkeit ist die Verwendung von Lambda-Ausdrücken als Initialisierer. Zu einem frühen Zeitpunkt in der Aufrufhierarchie wissen Sie, welche Objekte Sie instanziieren müssen (z. B. wenn Sie einen Verweis auf Ihr GraphicsDeviceManager-Objekt erstellen oder erhalten). Sobald Sie dies wissen, übergeben Sie den Lambda

() => new Triangle(manager) 

zu den nachfolgenden Methoden, damit sie wissen, wie sie von da an ein Dreieck erstellen können. Wenn Sie nicht alle möglichen Methoden bestimmen können, die Sie benötigen, können Sie immer ein Wörterbuch der Typen erstellen, die IDrawable mithilfe von Reflection implementieren, und den oben gezeigten Lambda-Ausdruck in einem Wörterbuch registrieren, das Sie entweder an einem gemeinsamen Ort speichern oder an weitere Funktionsaufrufe weitergeben können.

7voto

JTtheGeek Punkte 1683

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, die Konstruktion in eine separate Fabrik auszulagern. Zum Beispiel habe ich eine abstrakte Klasse namens IQueueItem, und ich brauche eine Möglichkeit, dieses Objekt zu und von einem anderen Objekt (CloudQueueMessage) zu übersetzen. Also auf der Schnittstelle IQueueItem habe ich -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

Jetzt brauche ich auch eine Möglichkeit für meine eigentliche Warteschlangenklasse, um eine CloudQueueMessage zurück in ein IQueueItem zu übersetzen - d.h. die Notwendigkeit für eine statische Konstruktion wie IQueueItem objMessage = ItemType.FromMessage. Stattdessen habe ich eine weitere Schnittstelle IQueueFactory definiert -

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

Jetzt kann ich endlich meine generische Warteschlangenklasse ohne die new()-Einschränkung schreiben, die in meinem Fall das Hauptproblem war.

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

jetzt kann ich eine Instanz erstellen, die die Kriterien für mich erfüllt

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

hoffentlich hilft das hier irgendwann jemandem, offensichtlich wurde viel interner Code entfernt, um das Problem und die Lösung zu zeigen

5voto

Matthew Punkte 686

Der generische Fabrikansatz scheint immer noch ideal zu sein. Man würde wissen, dass die Fabrik einen Parameter benötigt, und es würde einfach so passieren, dass diese Parameter an den Konstruktor des zu instanzierenden Objekts weitergegeben werden.

Hinweis: Dies ist nur syntaxgeprüfter Pseudocode, möglicherweise gibt es einen Laufzeitvorbehalt, den ich hier übersehe:

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
}

Ein Beispiel für eine mögliche Verwendung:

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

Zugegeben, Sie würden nur Instanzen über die Fabrik erstellen wollen, um sicherzustellen, dass Sie immer ein entsprechend initialisiertes Objekt haben. Vielleicht verwenden Sie ein Dependency Injection Framework wie AutoFac wäre sinnvoll; Update() könnte den IoC-Container nach einem neuen GraphicsDeviceManager-Objekt "fragen".

0 Stimmen

Es sieht so aus, als könnten Sie die Einschränkungen auf der Schnittstelle belassen, aber es gibt keine Möglichkeit für den Compiler zu wissen, dass die Fabrik etwas zurückgeben wird, das sie implementiert, also implementieren Sie einfach implizit IDrawableFactory public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager)

0 Stimmen

Hahah Ich schrieb meine Antwort, bevor ich Ihre bemerkte Matt, sieht aus wie wir denken gleich, aber ich denke, Sie sollten die generische auf der Schnittstelle selbst mit der Where-Klausel verwenden, um den Typ zu sperren

0 Stimmen

@JTtheGeek - Ich glaube, ich verstehe teilweise, aber es scheint mir, es würde meine Fabrik zu starr machen, eher wie eine abstrakte Basisklasse überschreiben. Ich müsste ein komplett neues Fabrikobjekt instanziieren, um an die zugrunde liegenden Typen zu gelangen, richtig? Aus diesem Grund habe ich die Einschränkungen nur auf die Builder-Methode angewendet, aber vielleicht habe ich das Ziel verfehlt. Vielleicht könnten Sie ein Beispiel für Ihre Änderungen posten, damit ich es besser verstehen kann. Die Notwendigkeit, null zu übergeben, um ein Draw-Objekt zu erstellen, um die Parameteranforderungen zu erfüllen, obwohl Draw einen leeren, Standard-Ctor haben kann, ist definitiv ein Nachteil für meinen Ansatz.

3voto

ghord Punkte 12117

Sie könnten dies mit Generika Trick zu tun, aber es ist immer noch anfällig für was Jon Skeet schrieb:

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

Klassen, die diese Schnittstelle implementieren, müssen einen parameterlosen Konstruktor haben:

public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}

1 Stimmen

Ein paar Vorbehalte: 1. Die Einschränkung stammt aus new() Die Überprüfung der eigenen Schnittstelle ist von eher begrenztem Nutzen / Overkill. 2. Sobald Sie Konstruktoren deklarieren, hört der Compiler auf, automatisch einen parameterlosen Konstruktor zu generieren. Einige Leser werden das in Ihrem Code-Beispiel vielleicht nicht erkennen. 3. Die Überprüfung von Klassen / Instanzen auf generische Schnittstellen ist umständlich. 4. Was Jon Skeet sagte

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