奇怪的是这是我第一次遇到这个问题,但是:
如何在 C# 接口中定义构造函数?
编辑有些人想要一个例子(这是一个空闲时间项目,所以是的,这是一个游戏)
IDdrawable +更新 +Draw
为了能够更新(检查屏幕边缘等)并绘制自身,它总是需要一个 GraphicsDeviceManager
。所以我想确保该对象具有对它的引用。这将属于构造函数。
既然我写下来了,我认为我在这里实现的是 IObservable
而 GraphicsDeviceManager
应该采用 IDrawable
...看来要么我没有得到 XNA 框架,要么没有考虑到框架很好。
编辑在接口的上下文中我对构造函数的定义似乎有些混乱。接口确实不能被实例化,所以不需要构造函数。我想要定义的是构造函数的签名。就像接口可以定义某个方法的签名一样,接口也可以定义构造函数的签名。
你不能。偶尔会很痛苦,但无论如何你都无法使用正常的技术来调用它。
在一篇博文中,我建议了 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)
{
}
}
如前所述,您不能在接口上拥有构造函数。但由于这是大约 7 年后在 Google 中排名很高的结果,我想我会在这里插话——特别是展示如何将抽象基类与现有接口结合使用,并可能减少重构量将来需要类似的情况。一些评论中已经暗示了这个概念,但我认为值得展示如何实际做到这一点。
因此,到目前为止,您的主界面如下所示:
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 构造函数,您可以创建一个类来定义您需要传入的所有字段的属性。
一个非常晚的贡献展示了接口构造函数的另一个问题。 (我选择这个问题是因为它最清楚地表达了问题)。假设我们可以:
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);
}
你不能。
接口定义了其他对象实现的契约,因此没有需要初始化的状态。
如果您有一些需要初始化的状态,您应该考虑使用抽象基类。
我回顾了这个问题,我心想,也许我们以错误的方式解决了这个问题。当涉及定义具有某些参数的构造函数时,接口可能不是要走的路……但是(抽象)基类是。
如果您在那里创建一个带有构造函数的基类,该构造函数接受您需要的参数,那么从它派生的每个类都需要提供它们。
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
{
}
}
不可能创建定义构造函数的接口,但是可以定义一个强制类型具有无参数构造函数的接口,尽管它是使用泛型的非常丑陋的语法......我实际上不太确定这确实是一个很好的编码模式。
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;
}
}
希望这可以帮助。
我发现解决这个问题的一种方法是将建筑分离成一个单独的工厂。例如,我有一个名为 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())
希望有一天这可以帮助其他人,显然删除了很多内部代码以尝试显示问题和解决方案
解决这个问题的一种方法是利用泛型和 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 表达式,你可以存储在共享位置或传递给进一步的函数调用。
通用工厂方法似乎仍然很理想。您会知道工厂需要一个参数,而这些参数恰好被传递给正在实例化的对象的构造函数。
请注意,这只是经过语法验证的伪代码,我在这里可能缺少运行时警告:
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 对象。
public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager)
您可以使用泛型技巧来做到这一点,但它仍然容易受到 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
}
new()
,检查自己的界面相当有限的使用/过度杀伤。 2. 一旦你声明了构造函数,编译器就会停止自动生成一个无参数的构造函数。有些读者可能无法从您的代码示例中了解这一点。 3. 检查泛型接口的类/实例不方便。 4. Jon Skeet 所说的
接口的目的是强制执行某个对象签名。它不应该明确地关心对象在内部是如何工作的。因此,从概念的角度来看,接口中的构造函数并不真正有意义。
虽然有一些选择:
创建一个充当最小默认实现的抽象类。该类应该具有您期望实现类具有的构造函数。
如果您不介意过度杀伤,请使用 AbstractFactory 模式并在工厂类接口中声明一个具有所需签名的方法。
将 GraphicsDeviceManager 作为参数传递给 Update 和 Draw 方法。
使用组合式面向对象编程框架将 GraphicsDeviceManager 传递到需要它的对象部分。在我看来,这是一个非常实验性的解决方案。
你所描述的情况一般都不容易处理。类似的情况是业务应用程序中需要访问数据库的实体。
BlogPost
,对象创建(可能在从数据库加载其数据之后)和实际的博客文章创建是两个不同的事件。
你没有。
构造函数是可以实现接口的类的一部分。接口只是类必须实现的方法的契约。
如果可以在接口中定义构造函数,那将非常有用。
鉴于接口是必须以指定方式使用的合同。对于某些情况,以下方法可能是可行的替代方案:
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;
}
}
强制某种构造函数的一种方法是在接口中仅声明 Getters
,这可能意味着实现类必须有一个方法,最好是构造函数,为其设置值 (private
ly)。
虽然您不能在接口中定义构造函数签名,但我觉得值得一提的是,这可能是考虑抽象类的地方。抽象类可以以与接口相同的方式定义未实现的(抽象)方法签名,但也可以具有已实现的(具体)方法和构造函数。
缺点是,因为它是一种类,它不能用于接口可以的任何多继承类型场景。
我使用以下模式使其防弹。
从基类派生他的类的开发人员不能意外地创建一个公共可访问的构造函数
最终类开发人员被迫通过通用的create方法
一切都是类型安全的,不需要铸件
它是 100% 灵活的,可以在任何地方重用,您可以在其中定义自己的基类。
尝试一下,如果不对基类进行修改,就无法破坏它(除非您定义了一个过时的标志而没有将错误标志设置为 true,但即使这样您最终也会收到警告) public abstract class Base
编辑:这是一个基于您的绘图示例的示例,它甚至强制执行接口抽象
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());
}
如果我对 OP 的理解正确,我们希望执行一个契约,其中 GraphicsDeviceManager 总是通过实现类来初始化。我有一个类似的问题,我正在寻找一个更好的解决方案,但这是我能想到的最好的:
将 SetGraphicsDeviceManager(GraphicsDeviceManager gdo) 添加到接口,这样实现类将被迫编写需要从构造函数调用的逻辑。
从 C# 8.0 开始,接口成员可以声明主体。这称为默认实现。具有主体的成员允许接口为不提供覆盖实现的类和结构提供“默认”实现。此外,从 C# 8.0 开始,接口可能包括:
常量运算符静态构造函数。嵌套类型 静态字段、方法、属性、索引器和事件 使用显式接口实现语法的成员声明。显式访问修饰符(默认访问是公共的)。
不定期副业成功案例分享