这是我第一次碰到这个问题,这很奇怪,但是:
如何在C#界面中定义构造函数?
修改
有些人想要一个例子(这是一个自由时间项目,所以是的,这是一个游戏)
IDrawable
+更新
+绘制
为了能够更新(检查屏幕边缘等)并绘制自己,它总是需要GraphicsDeviceManager
。所以我想确保对象有引用它。这将属于构造函数。
现在我写下来了,我认为我在这里实现的是IObservable
而GraphicsDeviceManager
应该采用IDrawable
...
似乎要么我没有得到XNA框架,要么框架没有被很好地考虑。
修改
关于我在接口上下文中对构造函数的定义似乎有些混乱。实际上不能实例化接口,因此不需要构造函数。我想要定义的是构造函数的签名。正如接口可以定义某个方法的签名,接口可以定义构造函数的签名。
答案 0 :(得分:307)
你做不到。它偶尔会很痛苦,但无论如何你都无法使用普通技术来调用它。
在博客文章中,我建议static interfaces只能在泛型类型约束中使用 - 但可能非常方便,IMO。
关于如果你可以在接口中定义构造函数,有一点是你在导出类时遇到了麻烦:
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)
{
}
}
答案 1 :(得分:133)
一个非常晚的贡献,展示了接口构造函数的另一个问题。 (我选择这个问题是因为它对问题有最明确的阐述)。假设我们可以:
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);
}
答案 2 :(得分:109)
如前所述,您不能在接口上拥有构造函数。但是由于大约7年后谷歌的排名很高,我认为我会在这里筹码 - 特别是为了展示你如何使用抽象基类与你现有的界面相结合,并可能减少重构的数量未来需要类似的情况。在一些评论中已经暗示了这个概念,但我认为值得展示如何实际做到这一点。
所以到目前为止你的主界面看起来像这样:
public interface IDrawable
{
void Update();
void Draw();
}
现在使用要强制执行的构造函数创建一个抽象类。实际上,既然从你编写原始问题开始就可以使用它,我们可以在这里使用泛型,并在这种情况下使用泛型,这样我们就可以将其调整到可能需要相同功能但具有不同构造函数要求的其他接口: / p>
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构造函数,可以创建一个类来定义您需要传入的所有字段的属性。
答案 3 :(得分:60)
你不能。
接口定义其他对象实现的合同,因此没有需要初始化的状态。
如果你有一些需要初始化的状态,你应该考虑使用抽象基类。
答案 4 :(得分:19)
不可能创建一个定义构造函数的接口,但是 可以定义一个强制类型具有无参数构造函数的接口,尽管它是一个使用泛型的非常难看的语法......我实际上并不确定这是一个很好的编码模式。
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;
}
}
希望这有帮助。
答案 5 :(得分:19)
我正在回顾这个问题,我想,也许我们正在以错误的方式解决这个问题。当涉及使用某些参数定义构造函数时,接口可能不是可行的方法......但是(抽象)基类是。
如果你创建一个带有构造函数的基类,它接受你需要的参数,那么从中获取的每个类都需要提供它们。
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
{
}
}
答案 6 :(得分:6)
我发现解决这个问题的一种方法是将建筑分隔成一个单独的工厂。例如,我有一个名为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())
希望有一天这会帮助其他人,显然很多内部代码被移除以试图显示问题和解决方案
答案 7 :(得分:5)
通用工厂方法似乎仍然很理想。您会知道工厂需要一个参数,而这些参数会被传递给正在实例化的对象的构造函数。
注意,这只是语法验证的伪代码,可能会有一个运行时警告我在这里缺少:
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);
}
当然,您只需要通过工厂的创建实例来保证您始终拥有适当的初始化对象。也许使用像AutoFac这样的依赖注入框架是有意义的; Update()可以“询问”IoC容器以获取新的GraphicsDeviceManager对象。
答案 8 :(得分:3)
解决此问题的一种方法是利用泛型和new()约束。
您可以将其表示为工厂类/接口,而不是将构造函数表示为方法/函数。如果在每个需要创建类对象的调用站点上指定new()泛型约束,则可以相应地传递构造函数参数。
对于您的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
}
另一种方法是使用lambda表达式作为初始化器。在调用层次结构的早期某个时刻,您将知道需要实例化哪些对象(即,在创建或获取对GraphicsDeviceManager对象的引用时)。一旦你拥有它,传递lambda
() => new Triangle(manager)
后续方法,以便他们知道从那时起如何创建一个三角形。如果您无法确定所需的所有可能方法,则始终可以创建使用反射实现IDrawable的类型字典,并在字典中注册上面显示的lambda表达式,您可以将其存储在共享位置或传递给进一步的函数调用。
答案 9 :(得分:2)
你可以用泛型技巧做到这一点,但它仍然容易受到Jon Skeet所写的影响:
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 :(得分:1)
接口的目的是强制执行某个对象签名。它应该明确地不关心对象如何在内部工作。因此,从概念的角度来看,接口中的构造函数并不真正有意义。
但有一些替代方案:
创建一个充当最小默认实现的抽象类。 该类应该具有您期望实现类的构造函数 拥有。
如果你不介意矫枉过正,请使用AbstractFactory模式和 声明工厂类接口中具有必需的方法 签名。
将GraphicsDeviceManager
作为参数传递给Update
和Draw
方法。
使用面向对象的组合编程框架将GraphicsDeviceManager
传递到需要它的对象部分。在我看来,这是一个非常实验性的解决方案。
您描述的情况一般不容易处理。类似的情况是业务应用程序中需要访问数据库的实体。
答案 11 :(得分:0)
后来,但有人需要。
您可以使用Func<TResult>
void Create(Func<IDrawable> func)
{
IDrawable result = func.Invoke();
}
GraphicsDeviceManager graphicsDeviceManager =...;
xxx.Create(() => new Draw(graphicsDeviceManager));
yyy.Create(() => new Update(graphicsDeviceManager));
答案 12 :(得分:0)
我使用以下模式使其防弹。
尝试一下,不对基类进行修改就不能破坏它(除了 如果您定义了一个过时的标志,而没有将错误标志设置为true,那么即使这样您仍然会收到警告)
public abstract class Base<TSelf, TParameter>
where TSelf : Base<TSelf, TParameter>, new()
{
protected const string FactoryMessage = "Use YourClass.Create(...) instead";
public static TSelf Create(TParameter parameter)
{
var me = new TSelf();
me.Initialize(parameter);
return me;
}
[Obsolete(FactoryMessage, true)]
protected Base()
{
}
protected virtual void Initialize(TParameter parameter)
{
}
}
public abstract class BaseWithConfig<TSelf, TConfig>: Base<TSelf, TConfig>
where TSelf : BaseWithConfig<TSelf, TConfig>, new()
{
public TConfig Config { get; private set; }
[Obsolete(FactoryMessage, true)]
protected BaseWithConfig()
{
}
protected override void Initialize(TConfig parameter)
{
this.Config = parameter;
}
}
public class MyService : BaseWithConfig<MyService, (string UserName, string Password)>
{
[Obsolete(FactoryMessage, true)]
public MyService()
{
}
}
public class Person : Base<Person, (string FirstName, string LastName)>
{
[Obsolete(FactoryMessage,true)]
public Person()
{
}
protected override void Initialize((string FirstName, string LastName) parameter)
{
this.FirstName = parameter.FirstName;
this.LastName = parameter.LastName;
}
public string LastName { get; private set; }
public string FirstName { get; private set; }
}
[Test]
public void FactoryTest()
{
var notInitilaizedPerson = new Person(); // doesn't compile because of the obsolete attribute.
Person max = Person.Create(("Max", "Mustermann"));
Assert.AreEqual("Max",max.FirstName);
var service = MyService.Create(("MyUser", "MyPassword"));
Assert.AreEqual("MyUser", service.Config.UserName);
}
编辑: 这是一个基于您的绘图示例的示例,该示例甚至可以强制接口抽象
public abstract class BaseWithAbstraction<TSelf, TInterface, TParameter>
where TSelf : BaseWithAbstraction<TSelf, TInterface, TParameter>, TInterface, new()
{
[Obsolete(FactoryMessage, true)]
protected BaseWithAbstraction()
{
}
protected const string FactoryMessage = "Use YourClass.Create(...) instead";
public static TInterface Create(TParameter parameter)
{
var me = new TSelf();
me.Initialize(parameter);
return me;
}
protected virtual void Initialize(TParameter parameter)
{
}
}
public abstract class BaseWithParameter<TSelf, TInterface, TParameter> : BaseWithAbstraction<TSelf, TInterface, TParameter>
where TSelf : BaseWithParameter<TSelf, TInterface, TParameter>, TInterface, new()
{
protected TParameter Parameter { get; private set; }
[Obsolete(FactoryMessage, true)]
protected BaseWithParameter()
{
}
protected sealed override void Initialize(TParameter parameter)
{
this.Parameter = parameter;
this.OnAfterInitialize(parameter);
}
protected virtual void OnAfterInitialize(TParameter parameter)
{
}
}
public class GraphicsDeviceManager
{
}
public interface IDrawable
{
void Update();
void Draw();
}
internal abstract class Drawable<TSelf> : BaseWithParameter<TSelf, IDrawable, GraphicsDeviceManager>, IDrawable
where TSelf : Drawable<TSelf>, IDrawable, new()
{
[Obsolete(FactoryMessage, true)]
protected Drawable()
{
}
public abstract void Update();
public abstract void Draw();
}
internal class Rectangle : Drawable<Rectangle>
{
[Obsolete(FactoryMessage, true)]
public Rectangle()
{
}
public override void Update()
{
GraphicsDeviceManager manager = this.Parameter;
// TODo manager
}
public override void Draw()
{
GraphicsDeviceManager manager = this.Parameter;
// TODo manager
}
}
internal class Circle : Drawable<Circle>
{
[Obsolete(FactoryMessage, true)]
public Circle()
{
}
public override void Update()
{
GraphicsDeviceManager manager = this.Parameter;
// TODo manager
}
public override void Draw()
{
GraphicsDeviceManager manager = this.Parameter;
// TODo manager
}
}
[Test]
public void FactoryTest()
{
// doesn't compile because interface abstraction is enforced.
Rectangle rectangle = Rectangle.Create(new GraphicsDeviceManager());
// you get only the IDrawable returned.
IDrawable service = Circle.Create(new GraphicsDeviceManager());
}
答案 13 :(得分:0)
接口是合约接口不允许字段,即无需初始化任何内容。接口仅保留引用。接口必须要求派生类来保存内存。
答案 14 :(得分:0)
虽然您无法在界面中定义构造函数签名,但我觉得值得一提的是,这可能是考虑抽象类的地方。抽象类可以以与接口相同的方式定义未实现的(抽象)方法签名,但也可以实现(具体)方法和构造函数。
缺点是,因为它是一种类,所以它不能用于接口可以的任何多继承类型场景。
答案 15 :(得分:0)
强制某种构造函数的一种方法是在接口中仅声明Getters
,这可能意味着实现类必须有一个方法,理想情况下是构造函数,才能设置值({{1}对它来说。
答案 16 :(得分:0)
如果可以在接口中定义构造函数,那将非常有用。
鉴于接口是必须以指定方式使用的合同。对于某些情况,以下方法可能是一种可行的替代方法:
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;
}
}
答案 17 :(得分:0)
你没有。
构造函数是可以实现接口的类的一部分。接口只是类必须实现的方法的契约。
答案 18 :(得分:-1)
如果我正确理解OP,我们希望强制执行一个契约,其中GraphicsDeviceManager总是通过实现类来初始化。我有一个类似的问题,我正在寻找一个更好的解决方案,但这是我能想到的最好的:
将SetGraphicsDeviceManager(GraphicsDeviceManager gdo)添加到接口,这样实现类将被强制编写一个需要从构造函数调用的逻辑。
答案 19 :(得分:-5)
从C#8.0开始,接口成员可以声明主体。这称为默认实现。具有主体的成员允许接口为不提供替代实现的类和结构提供“默认”实现。另外,从C#8.0开始,接口可能包括:
常量 经营者 静态构造函数。 嵌套类型 静态字段,方法,属性,索引器和事件 使用显式接口实现语法的成员声明。 显式访问修饰符(默认访问权限为public)。