Интерфейс, определяющий подпись конструктора?
Странно, что это первый раз, когда я столкнулся с этой проблемой, но:
Как вы определяете конструктор в интерфейсе С#?
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), и таким образом классы реализации будут вынуждены писать логику, которая потребует вызова от конструктора.