Интерфейс, определяющий подпись конструктора?

Странно, что это первый раз, когда я столкнулся с этой проблемой, но:

Как вы определяете конструктор в интерфейсе С#?

Edit
Некоторым людям нужен пример (это проект свободного времени, так что да, это игра)

IDrawable
 + Обновление
 + Draw

Чтобы иметь возможность обновлять (проверять край экрана и т.д.) и рисовать, ему всегда понадобится GraphicsDeviceManager. Поэтому я хочу убедиться, что объект имеет ссылку на него. Это будет принадлежать конструктору.

Теперь, когда я записал это, я думаю, что я реализую здесь IObservable, а GraphicsDeviceManager должен взять IDrawable... Кажется, что я не получаю рамки XNA, или рамки не очень хорошо продумываются.

Edit
Кажется, есть некоторая путаница в моем определении конструктора в контексте интерфейса. Интерфейс действительно не может быть создан, поэтому конструктор не нуждается. То, что я хотел определить, было сигнатурой для конструктора. Точно так же, как интерфейс может определять подпись определенного метода, интерфейс может определять подпись конструктора.

Ответ 1

Как уже было отмечено, вы не можете иметь конструкторы интерфейса. Но так как это очень высокий результат в Google примерно через 7 лет, я думал, что я буду чип здесь, в частности, чтобы показать, как вы можете использовать абстрактный базовый класс в тандеме с вашим существующим интерфейсом и, возможно, сократить объем рефакторинга необходимых в будущем для подобных ситуаций. Эта концепция уже упоминалась в некоторых комментариях, но я подумал, что было бы полезно показать, как это сделать на самом деле.

Итак, у вас есть свой основной интерфейс, который выглядит так:

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

Теперь создайте абстрактный класс с конструктором, который вы хотите применить. Фактически, поскольку он теперь доступен с момента написания вашего оригинального вопроса, мы можем немного притворяться здесь и использовать в этой ситуации дженерики, чтобы мы могли адаптировать его к другим интерфейсам, которые могут нуждаться в одинаковой функциональности, но имеют разные требования к конструкторам:

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

    }
}

Теперь вам нужно создать новый класс, который наследуется как от интерфейса IDrawable, так и от абстрактного класса MustInitialize:

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

Затем просто создайте экземпляр Drawable, и вы хорошо пойдете:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

Приятно то, что новый класс Drawable, который мы создали, все еще ведет себя так же, как ожидалось от IDrawable.

Если вам нужно передать более одного параметра в конструктор MustInitialize, вы можете создать класс, который определяет свойства для всех полей, которые вам нужно передать.

Ответ 2

Вы не можете. Иногда это боль, но в любом случае вы не сможете назвать ее обычными методами.

В сообщении в блоге я предложил статические интерфейсы, которые могли бы использоваться только в ограничениях типа общего типа, но могут быть очень удобными, ИМО.

Один вопрос о том, можно ли определить конструктор в интерфейсе, у вас возникнут проблемы с получением классов:

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

Ответ 3

Очень поздний вклад, демонстрирующий еще одну проблему с сопряженными конструкторами. (Я выбираю этот вопрос, потому что он имеет четкое изложение проблемы). Предположим, что мы могли бы:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

Если по соглашению реализация "конструктора интерфейса" заменяется именем типа.

Теперь создайте экземпляр:

ICustomer a = new Person("Ernie");

Можно ли сказать, что контракт ICustomer выполняется?

А как насчет этого:

interface ICustomer
{
    ICustomer(string address);
}

Ответ 4

Вы не можете.

Интерфейсы определяют контракты, которые реализуются другими объектами, и поэтому не имеют состояния, которое необходимо инициализировать.

Если у вас есть какое-то состояние, которое нужно инициализировать, вы должны использовать вместо него абстрактный базовый класс.

Ответ 5

Невозможно создать интерфейс, который определяет конструкторы, но он может определить интерфейс, который заставляет тип иметь конструктор без параметров, хотя он будет очень уродливым синтаксисом, который использует generics... На самом деле я не уверен, что это действительно хороший код.

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
}

С другой стороны, если вы хотите проверить, имеет ли тип конструктор без параметров, вы можете сделать это с помощью отражения:

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

Надеюсь, что это поможет.

Ответ 6

Я оглядывался назад на этот вопрос, и я подумал про себя: возможно, мы ошибаемся в этой проблеме. Интерфейсы, возможно, не подходят для определения конструктора с определенными параметрами... но базовый класс (абстрактный).

Если вы создаете базовый класс с конструктором, который принимает нужные вам параметры, каждый класс, который от него нуждается, должен их предоставить.

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

Ответ 7

Общий подход factory по-прежнему кажется идеальным. Вы знаете, что для параметра factory требуется параметр, и было бы так, что эти параметры передаются вместе с конструктором создаваемого объекта.

Обратите внимание, что это просто подтвержденный синтаксисом псевдокод, может быть предостережение во время выполнения, которое я здесь отсутствует:

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
}

Пример возможного использования:

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

Конечно, вам нужно только создать экземпляры с помощью factory, чтобы гарантировать, что у вас всегда есть правильно инициализированный объект. Возможно, использование концепции инъекций зависимостей, например AutoFac, имеет смысл; Update() может "спросить" контейнер IoC для нового объекта GraphicsDeviceManager.

Ответ 8

Один из способов решить эту проблему, которую я нашел, - это разделить конструкцию на отдельный factory. Например, у меня есть абстрактный класс с именем IQueueItem, и мне нужен способ перевести этот объект на другой объект (CloudQueueMessage) и обратно. Итак, на интерфейсе IQueueItem у меня есть -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

Теперь мне также нужно, чтобы мой фактический класс очереди переводил CloudQueueMessage обратно в объект IQueueItem, т.е. необходимость создания статической конструкции, такой как IQueueItem objMessage = ItemType.FromMessage. Вместо этого я определил другой интерфейс IQueueFactory -

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

Теперь я могу, наконец, написать свой общий класс очереди без ограничения new(), которое в моем случае было основной проблемой.

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

теперь я могу создать экземпляр, который удовлетворяет критериям для меня

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

надеюсь, это поможет кому-то еще когда-нибудь, очевидно, что много внутреннего кода удалено, чтобы попытаться показать проблему и решение

Ответ 9

Вы можете сделать это с помощью трюка generics, но он все еще уязвим для того, что написал Джон Скит:

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

Класс, реализующий этот интерфейс, должен иметь конструктор без параметров:

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

Ответ 10

Одним из способов решения этой проблемы является использование дженериков и ограничение new().

Вместо выражения вашего конструктора как метода/функции вы можете выразить его как класс/интерфейс factory. Если вы укажете новое() общее ограничение на каждом сайте вызова, которому необходимо создать объект своего класса, вы сможете соответствующим образом передать аргументы конструктора.

Для примера с 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);
    } 
}

Теперь, когда вы его используете:

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
}

Вы даже можете сконцентрировать все методы создания в одном классе, используя явную реализацию интерфейса:

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

Чтобы использовать его:

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
}

Другой способ - использование лямбда-выражений в качестве инициализаторов. В какой-то момент в начале иерархии вызовов вы узнаете, какие объекты вам нужно создать (т.е. Когда вы создаете или получаете ссылку на объект GraphicsDeviceManager). Как только у вас есть это, пройдите лямбда

() => new Triangle(manager) 

для последующих методов, чтобы они знали, как создать Треугольник с этого момента. Если вы не можете определить все возможные методы, которые вам понадобятся, вы всегда можете создать словарь типов, которые реализуют IDrawable с помощью отражения, и зарегистрировать выражение лямбда, показанное выше, в словаре, которое вы можете хранить в общем месте или передавать дальнейшие вызовы функций.

Ответ 11

нет.

конструктор является частью класса, который может реализовать интерфейс. Интерфейс - это всего лишь контракт методов, которые должен реализовать класс.

Ответ 12

Было бы очень полезно, если бы можно было определить конструкторы в интерфейсах.

Учитывая, что интерфейс - это контракт, который должен использоваться указанным образом. Следующий подход может быть жизнеспособной альтернативой для некоторых сценариев:

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

}

Ответ 13

Один из способов принудительного создания какого-либо конструктора - объявить только интерфейс Getters в интерфейсе, что может означать, что у класса-реализации должен быть метод, в идеале конструктор, для набора значений (private ly) для он.

Ответ 14

Пока вы не можете определить подпись конструктора в интерфейсе, я считаю, что стоит упомянуть, что это может быть пятно для рассмотрения абстрактного класса. Абстрактные классы могут определять нереализованные (абстрактные) сигнатуры метода так же, как интерфейс, но также могут реализовывать (конкретные) методы и конструкторы.

Недостатком является то, что, поскольку это тип класса, он не может использоваться ни для одного из сценариев множественного типа наследования, которые могут иметь интерфейсы.

Ответ 15

Я снимаю этот поток из-за JavaScript, который передает его через прототипы.

Я также считаю, что конструктор не может преодолеть инициализацию своих полей. Особенно в С++, когда вы находитесь в конструкторе, вы не можете создать указатель "this". В С# это не так; Но это всего лишь недостаток принципов, и это преодолевает то, что происходит в конструкторе класса С++.

Далее, согласен, что любой класс должен гарантировать, что все поля правильно определены и не являются нулевыми. Это можно легко проверить с помощью простого частного атрибута или функции, которая проверяет, что что-то не так для нулевого поля или поля, на которое не ссылаются.

Ответ 16

Если я правильно понял OP, мы хотим обеспечить выполнение контракта, в котором GraphicsDeviceManager всегда инициализируется путем реализации классов. У меня была аналогичная проблема, и я искал лучшее решение, но это лучшее, что я могу придумать:

Добавьте в интерфейс SetGraphicsDeviceManager (GraphicsDeviceManager gdo), и таким образом классы реализации будут вынуждены писать логику, которая потребует вызова от конструктора.